From 56b79f12ac68244da801fdf4aa3e65837aba0386 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 21 May 2026 13:10:52 -0700 Subject: [PATCH] fix(dashboard): remove country flags from language picker (#29997) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #29750. Reporter flagged that 繁體中文 displayed the TW flag instead of the PRC flag. Rather than picking a side, drop the language-flag pairings entirely — languages aren't countries (English ≠ GB, Portuguese ≠ PT, Mandarin variants ≠ any single jurisdiction), and endonyms are unambiguous. - LOCALE_META: strip flagCountryCode field - LanguageSwitcher: remove LocaleFlagIcon component + both call sites - main.tsx: drop flag-icons CSS import - package.json: uninstall flag-icons --- web/package-lock.json | 34 ++------------------ web/package.json | 1 - web/src/components/LanguageSwitcher.tsx | 27 ++++------------ web/src/i18n/context.tsx | 42 ++++++++++++++----------- web/src/main.tsx | 1 - 5 files changed, 31 insertions(+), 74 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index e8990b61ab1..034d48a1f89 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -19,7 +19,6 @@ "@xterm/xterm": "^6.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "flag-icons": "^7.5.0", "gsap": "^3.15.0", "leva": "^0.10.1", "lucide-react": "^0.577.0", @@ -78,7 +77,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1129,7 +1127,6 @@ "resolved": "https://registry.npmjs.org/@observablehq/plot/-/plot-0.6.17.tgz", "integrity": "sha512-/qaXP/7mc4MUS0s4cPPFASDRjtsWp85/TbfsciqDgU1HwYixbSbbytNuInD8AcTYC3xaxACgVX06agdfQy9W+g==", "license": "ISC", - "peer": true, "dependencies": { "d3": "^7.9.0", "interval-tree-1d": "^1.0.0", @@ -1868,7 +1865,6 @@ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.0.tgz", "integrity": "sha512-90abYK2q5/qDM+GACs9zRvc5KhEEpEWqWlHSd64zTPNxg+9wCJvTfyD9x2so7hlQhjRYO1Fa6flR3BC/kpTFkA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", @@ -2574,7 +2570,6 @@ "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -2584,7 +2579,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2595,7 +2589,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2660,7 +2653,6 @@ "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", @@ -2989,7 +2981,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3142,7 +3133,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -3650,7 +3640,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -3970,7 +3959,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4250,12 +4238,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flag-icons": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-7.5.0.tgz", - "integrity": "sha512-kd+MNXviFIg5hijH766tt+3x76ele1AXlo4zDdCxIvqWZhKt4T83bOtxUOOMlTx/EcFdUMH5yvQgYlFh1EqqFg==", - "license": "MIT" - }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -4382,8 +4364,7 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz", "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==", - "license": "Standard 'no charge' license: https://gsap.com/standard-license.", - "peer": true + "license": "Standard 'no charge' license: https://gsap.com/standard-license." }, "node_modules/has-flag": { "version": "4.0.0", @@ -4698,7 +4679,6 @@ "resolved": "https://registry.npmjs.org/leva/-/leva-0.10.1.tgz", "integrity": "sha512-BcjnfUX8jpmwZUz2L7AfBtF9vn4ggTH33hmeufDULbP3YgNZ/C+ss/oO3stbrqRQyaOmRwy70y7BGTGO81S3rA==", "license": "MIT", - "peer": true, "dependencies": { "@radix-ui/react-portal": "^1.1.4", "@radix-ui/react-tooltip": "^1.1.8", @@ -5106,7 +5086,6 @@ "resolved": "https://registry.npmjs.org/motion/-/motion-12.39.0.tgz", "integrity": "sha512-H4a+Ze+a9j+/NTla5ezfb/g9vmIOxC+viDj++NGDZyTZkdRKjiOz3kSv6TalRWM8ZmD2y/CfC6TkQc97ybyqSA==", "license": "MIT", - "peer": true, "dependencies": { "framer-motion": "^12.39.0", "tslib": "^2.4.0" @@ -5179,7 +5158,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": "^20.0.0 || >=22.0.0" } @@ -5307,7 +5285,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5379,7 +5356,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5399,7 +5375,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5760,8 +5735,7 @@ "version": "0.180.0", "resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz", "integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tinyglobby": { "version": "0.2.16", @@ -5826,7 +5800,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5925,7 +5898,6 @@ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -5941,7 +5913,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -6063,7 +6034,6 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/web/package.json b/web/package.json index cdc95162234..7c4c60bfc68 100644 --- a/web/package.json +++ b/web/package.json @@ -21,7 +21,6 @@ "@xterm/xterm": "^6.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "flag-icons": "^7.5.0", "gsap": "^3.15.0", "leva": "^0.10.1", "lucide-react": "^0.577.0", diff --git a/web/src/components/LanguageSwitcher.tsx b/web/src/components/LanguageSwitcher.tsx index b56f604b1ba..097250d4cdf 100644 --- a/web/src/components/LanguageSwitcher.tsx +++ b/web/src/components/LanguageSwitcher.tsx @@ -9,15 +9,16 @@ import type { Locale } from "@/i18n"; import { cn } from "@/lib/utils"; /** - * Language picker — shows the current language's flag + endonym, opens a - * dropdown of all supported locales when clicked. Persists choice to - * localStorage via the I18n context. + * Language picker — shows the current language's endonym, opens a dropdown + * of all supported locales when clicked. Persists choice to localStorage via + * the I18n context. * * Replaces the older two-state EN↔ZH toggle now that we ship 16 locales * (en, zh, zh-hant, ja, de, es, fr, tr, uk, af, ko, it, ga, pt, ru, hu). * - * Locale markers use lipis/flag-icons (SVG sprites) instead of emoji so flags - * render consistently across platforms. + * No country flags by design — languages aren't countries, and flag pairings + * inevitably create political mismappings (e.g. Mandarin variants ≠ any single + * jurisdiction, English ≠ GB, Portuguese ≠ PT). Endonyms are unambiguous. * * When placed at the bottom of the sidebar (next to ThemeSwitcher), pass * `dropUp` so the list opens above the trigger and avoids clipping below the @@ -71,7 +72,6 @@ export function LanguageSwitcher({ dropUp = false }: LanguageSwitcherProps) { className="px-2 py-1 normal-case tracking-normal font-normal text-xs text-muted-foreground hover:text-foreground" > - - - {meta.name} {selected && } @@ -158,15 +156,6 @@ function LanguageSwitcherOptions({ ); } -function LocaleFlagIcon({ countryCode }: LocaleFlagIconProps) { - return ( - - ); -} - interface LanguageSwitcherOptionsProps { allLocales: Array<[Locale, (typeof LOCALE_META)[Locale]]>; locale: Locale; @@ -177,7 +166,3 @@ interface LanguageSwitcherOptionsProps { interface LanguageSwitcherProps { dropUp?: boolean; } - -interface LocaleFlagIconProps { - countryCode: string; -} diff --git a/web/src/i18n/context.tsx b/web/src/i18n/context.tsx index e31ffa65050..aa7a219b924 100644 --- a/web/src/i18n/context.tsx +++ b/web/src/i18n/context.tsx @@ -37,27 +37,31 @@ const TRANSLATIONS: Record = { }; // Display metadata for the language picker — endonym (native name) so users -// recognize their language even if they don't speak the current UI language, -// plus a flag-icons sprite (ISO 3166-1 alpha-2) for visual scanning. +// recognize their language even if they don't speak the current UI language. // Exposed as a constant so the LanguageSwitcher and any future settings page // can share the same list. -export const LOCALE_META: Record = { - en: { name: "English", flagCountryCode: "gb" }, - zh: { name: "简体中文", flagCountryCode: "cn" }, - "zh-hant": { name: "繁體中文", flagCountryCode: "tw" }, - ja: { name: "日本語", flagCountryCode: "jp" }, - de: { name: "Deutsch", flagCountryCode: "de" }, - es: { name: "Español", flagCountryCode: "es" }, - fr: { name: "Français", flagCountryCode: "fr" }, - tr: { name: "Türkçe", flagCountryCode: "tr" }, - uk: { name: "Українська", flagCountryCode: "ua" }, - af: { name: "Afrikaans", flagCountryCode: "za" }, - ko: { name: "한국어", flagCountryCode: "kr" }, - it: { name: "Italiano", flagCountryCode: "it" }, - ga: { name: "Gaeilge", flagCountryCode: "ie" }, - pt: { name: "Português", flagCountryCode: "pt" }, - ru: { name: "Русский", flagCountryCode: "ru" }, - hu: { name: "Magyar", flagCountryCode: "hu" }, +// +// We intentionally do NOT pair locales with country flags. Languages are not +// countries (English ≠ GB, Portuguese ≠ PT, Spanish ≠ ES, Chinese variants ≠ +// any single jurisdiction). Endonyms are unambiguous and avoid the political +// mismapping that flag pairings inevitably create. +export const LOCALE_META: Record = { + en: { name: "English" }, + zh: { name: "简体中文" }, + "zh-hant": { name: "繁體中文" }, + ja: { name: "日本語" }, + de: { name: "Deutsch" }, + es: { name: "Español" }, + fr: { name: "Français" }, + tr: { name: "Türkçe" }, + uk: { name: "Українська" }, + af: { name: "Afrikaans" }, + ko: { name: "한국어" }, + it: { name: "Italiano" }, + ga: { name: "Gaeilge" }, + pt: { name: "Português" }, + ru: { name: "Русский" }, + hu: { name: "Magyar" }, }; const SUPPORTED_LOCALES = Object.keys(TRANSLATIONS) as Locale[]; diff --git a/web/src/main.tsx b/web/src/main.tsx index c727f0e3f72..e0d00fdf636 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -1,6 +1,5 @@ import { createRoot } from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; -import "flag-icons/css/flag-icons.min.css"; import "./index.css"; import App from "./App"; import { SystemActionsProvider } from "./contexts/SystemActions";