From 28bce72274d47d497ed0c61480d29449d05f48c2 Mon Sep 17 00:00:00 2001 From: liangguifeng <1476982312@qq.com> Date: Tue, 14 Apr 2026 11:05:00 +0800 Subject: [PATCH 1/3] fix(header): guard missing translation progress in language menu --- src/components/Header/LanguageMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Header/LanguageMenu.tsx b/src/components/Header/LanguageMenu.tsx index 48fcd8f0ef..316daf05ec 100644 --- a/src/components/Header/LanguageMenu.tsx +++ b/src/components/Header/LanguageMenu.tsx @@ -30,7 +30,7 @@ const LangMap: Record = { }; const TranslationProgress = ({ lang }: { lang: string }) => { - const percent = i18nProgress[lang].percent; + const percent = i18nProgress[lang as keyof typeof i18nProgress]?.percent; if (typeof percent === 'number' && percent < 100) { return ( Date: Tue, 14 Apr 2026 13:18:08 +0800 Subject: [PATCH 2/3] fix(config): add missing 'tr' lang to i18nProgress plugin The root cause of #3300: vite.config.ts i18nProgress plugin was missing 'tr' (Turkish) in the langs array, causing i18nProgress['tr'] to be undefined when TranslationProgress component accessed it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.ts b/vite.config.ts index 799fdf323d..09887546c7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -91,7 +91,7 @@ export default defineConfig({ semicolons: false, }), i18nProgress({ - langs: ['en', 'es', 'de', 'zh'], + langs: ['en', 'es', 'de', 'zh', 'tr'], baseLang: 'en', getTranslationDir: (lang) => `./src/locales/${lang}`, }), From 1c6c064086dc44a10c18f07f1ce388af6af0a55c Mon Sep 17 00:00:00 2001 From: Baoyuan Date: Wed, 15 Apr 2026 11:05:56 +0800 Subject: [PATCH 3/3] test: add e2e test for language switching and auto-derive langs - Replace hardcoded langs array in vite.config.ts with readdirSync to auto-derive supported languages from src/locales/ directory - Add Playwright e2e test that switches through all languages and verifies no JS errors occur (regression test for #3300) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- e2e/tests/language-switch.spec.ts | 64 +++++++++++++++++++++++++++++++ vite.config.ts | 4 +- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 e2e/tests/language-switch.spec.ts diff --git a/e2e/tests/language-switch.spec.ts b/e2e/tests/language-switch.spec.ts new file mode 100644 index 0000000000..bf9351724a --- /dev/null +++ b/e2e/tests/language-switch.spec.ts @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test } from '@e2e/utils/test'; +import { expect } from '@playwright/test'; + +// All non-default languages to switch to (default is 'en') +const switchTargets = ['Deutsch', '中文', 'Español', 'Türkçe', 'English']; + +test( + 'switching all languages should not crash the page', + { tag: '@i18n' }, + async ({ page }) => { + const errors: string[] = []; + page.on('pageerror', (error) => errors.push(error.message)); + + for (const lang of switchTargets) { + await test.step(`switch to ${lang}`, async () => { + const langMenuTrigger = page.locator('button[aria-haspopup="menu"]'); + await langMenuTrigger.click(); + + const menuItem = page.getByRole('menuitem', { name: lang }); + await expect(menuItem).toBeVisible(); + await menuItem.click(); + + // Verify the page didn't crash + await expect(page.locator('header')).toBeVisible(); + }); + } + + expect(errors).toEqual([]); + } +); + +test( + 'language menu shows all supported languages', + { tag: '@i18n' }, + async ({ page }) => { + const allLanguages = ['English', 'Deutsch', '中文', 'Español', 'Türkçe']; + + const langMenuTrigger = page.locator('button[aria-haspopup="menu"]'); + await langMenuTrigger.click(); + + for (const lang of allLanguages) { + await expect( + page.getByRole('menuitem', { name: lang }) + ).toBeVisible(); + } + } +); diff --git a/vite.config.ts b/vite.config.ts index 09887546c7..cb00b0226c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -14,6 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { readdirSync } from 'node:fs'; + import { TanStackRouterVite } from '@tanstack/router-plugin/vite'; import react from '@vitejs/plugin-react-swc'; import observerPlugin from 'mobx-react-observer/swc-plugin'; @@ -91,7 +93,7 @@ export default defineConfig({ semicolons: false, }), i18nProgress({ - langs: ['en', 'es', 'de', 'zh', 'tr'], + langs: readdirSync('./src/locales'), baseLang: 'en', getTranslationDir: (lang) => `./src/locales/${lang}`, }),