Move language setting to primary interface section

Language is a first-class presentation preference alongside Theme and
Zoom, so it shouldn't be buried in the Advanced disclosure. Advanced
now contains only platform-chrome controls.
This commit is contained in:
Jinjing
2026-07-25 11:51:24 -07:00
parent 505967eba0
commit e69c210e0b
2 changed files with 48 additions and 34 deletions
@@ -66,13 +66,14 @@ export function AppearanceInterfaceSection({
const titlebarEntry = getTitlebarEntries()[0]
const typographyEntry = getTypographyEntries()[0]
const zoomEntry = getZoomEntries()[0]
// Language is primary (with Theme/Zoom/Font); Advanced is platform chrome only.
const advancedEntries = [
...(SHOW_UI_LANGUAGE_SETTING ? getLanguageEntries() : []),
...getTitlebarEntries(),
...getSystemTrayEntries({ showSystemTray: isDesktopWindows }),
...getMenuBarIconEntries({ showMenuBarIcon: isDesktopMac })
]
const showAdvanced = !isSearching || matchesSettingsSearch(searchQuery, advancedEntries)
const languageTitle = translate('settings.appearance.language.title', 'Language')
return (
<div className="divide-y divide-border/40">
@@ -111,6 +112,36 @@ export function AppearanceInterfaceSection({
/>
</SearchableSetting>
{SHOW_UI_LANGUAGE_SETTING ? (
<SearchableSetting
title={languageTitle}
description={languageEntry?.description}
keywords={languageEntry?.keywords ?? []}
forceVisible={forceVisiblePrimary}
>
<SettingsRow
label={languageTitle}
control={
<Select
value={settings.uiLanguage}
onValueChange={(value) => updateSettings({ uiLanguage: value as UiLanguage })}
>
<SelectTrigger size="sm" className="min-w-[220px]" aria-label={languageTitle}>
<SelectValue />
</SelectTrigger>
<SelectContent>
{UI_LANGUAGE_CHOICES.map((choice) => (
<SelectItem key={choice.value} value={choice.value}>
{getUiLanguageChoiceLabel(choice, translate)}
</SelectItem>
))}
</SelectContent>
</Select>
}
/>
</SearchableSetting>
) : null}
<SearchableSetting
title={translate('auto.components.settings.AppearancePane.5e6d7aba8d', 'UI Zoom')}
description={zoomEntry?.description}
@@ -161,39 +192,6 @@ export function AppearanceInterfaceSection({
{showAdvanced ? (
<AppearanceAdvancedDisclosure showTopBorder={false}>
<div className="divide-y divide-border/40">
{SHOW_UI_LANGUAGE_SETTING ? (
<SearchableSetting
title={translate('settings.appearance.language.title', 'Language')}
description={languageEntry?.description}
keywords={languageEntry?.keywords ?? []}
>
<SettingsRow
label={translate('settings.appearance.language.title', 'Language')}
control={
<Select
value={settings.uiLanguage}
onValueChange={(value) => updateSettings({ uiLanguage: value as UiLanguage })}
>
<SelectTrigger
size="sm"
className="min-w-[220px]"
aria-label={translate('settings.appearance.language.title', 'Language')}
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{UI_LANGUAGE_CHOICES.map((choice) => (
<SelectItem key={choice.value} value={choice.value}>
{getUiLanguageChoiceLabel(choice, translate)}
</SelectItem>
))}
</SelectContent>
</Select>
}
/>
</SearchableSetting>
) : null}
<SearchableSetting
title={translate(
'auto.components.settings.AppearancePane.9868f39007',
@@ -202,6 +202,22 @@ describe('AppearancePane', () => {
delete (window as unknown as { api?: unknown }).api
})
it('shows language as a primary interface control without opening Advanced', async () => {
// Language is a first-class presentation pref next to Theme — browsing
// Appearance → Interface must surface it without expanding Advanced.
mocks.state.settingsSearchQuery = ''
const container = await renderAppearancePane(getDefaultSettings('/tmp'))
const languageTrigger = container.querySelector<HTMLButtonElement>(
'[data-slot="select-trigger"][aria-label="Language"]'
)
expect(languageTrigger).not.toBeNull()
// Advanced platform-chrome controls stay collapsed by default.
expect(
container.querySelector('button[role="switch"][aria-label="Titlebar App Name"]')
).toBeNull()
})
it('renders the language dropdown with system, english, chinese, korean, japanese, and spanish options', async () => {
mocks.state.settingsSearchQuery = 'language'
const updateSettings = vi.fn()