fix(web): align language switcher fallback

This commit is contained in:
XiaoSeS 2026-09-17 14:50:53 +08:00 committed by GitHub
parent c888be7212
commit 0d4f149e8a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 67 additions and 14 deletions

View file

@ -1,12 +0,0 @@
import { describe, expect, it } from 'vitest'
import * as mod from './language-switcher'
/**
* LanguageSwitcher renders an in-tree locale menu (no Radix portal) driven by i18next.
* There are no exported pure helpers; verify the export contract only.
*/
describe('language-switcher module exports', () => {
it('exports the LanguageSwitcher component', () => {
expect(mod.LanguageSwitcher).toBeTypeOf('function')
})
})

View file

@ -0,0 +1,64 @@
// @vitest-environment jsdom
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { LanguageSwitcher } from './language-switcher'
const mockI18n = vi.hoisted(() => ({
language: undefined as string | undefined,
resolvedLanguage: undefined as string | undefined,
changeLanguage: vi.fn(),
}))
vi.mock('react-i18next', () => ({
useTranslation: () => ({ i18n: mockI18n }),
}))
describe('LanguageSwitcher', () => {
beforeEach(() => {
mockI18n.language = undefined
mockI18n.resolvedLanguage = undefined
mockI18n.changeLanguage.mockImplementation((language: string) => {
mockI18n.language = language
mockI18n.resolvedLanguage = language
return Promise.resolve()
})
})
afterEach(() => {
cleanup()
vi.clearAllMocks()
})
it('exports the LanguageSwitcher component', () => {
expect(LanguageSwitcher).toBeTypeOf('function')
})
it('falls back to English when i18next has not resolved a language yet', () => {
render(<LanguageSwitcher />)
expect(screen.getByRole('button', { name: /English/i })).toBeTruthy()
})
it('uses the resolved language before the raw detected language', () => {
mockI18n.language = 'zh-CN'
mockI18n.resolvedLanguage = 'en'
render(<LanguageSwitcher />)
expect(screen.getByRole('button', { name: /English/i })).toBeTruthy()
})
it('updates the displayed language after selecting English', () => {
mockI18n.language = 'zh'
mockI18n.resolvedLanguage = 'zh'
render(<LanguageSwitcher />)
fireEvent.click(screen.getByRole('button', { name: /中文/i }))
fireEvent.click(screen.getByRole('menuitem', { name: /English/i }))
expect(mockI18n.changeLanguage).toHaveBeenCalledWith('en')
expect(screen.getByRole('button', { name: /English/i })).toBeTruthy()
})
})

View file

@ -27,9 +27,10 @@ export function LanguageSwitcher({ className }: LanguageSwitcherProps) {
{ code: 'ru', name: 'Русский' },
]
const fallbackLanguage = languages.find((lang) => lang.code === 'en') ?? languages[0]
// Primary language code only (strip region, e.g. ru-RU → ru).
const currentLangCode = i18n.language?.split('-')[0] || 'zh'
const currentLanguage = languages.find((lang) => lang.code === currentLangCode) || languages[0]
const currentLangCode = (i18n.resolvedLanguage || i18n.language || fallbackLanguage.code).split('-')[0]
const currentLanguage = languages.find((lang) => lang.code === currentLangCode) || fallbackLanguage
useEffect(() => {
if (!open) {