feat: added French support
This commit is contained in:
@@ -3,6 +3,7 @@ import { beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import ChatBox from './ChatBox';
|
||||
import { I18nContext } from '../i18n/I18nProvider';
|
||||
import type { ResolvedLocaleCode } from '../i18n/types';
|
||||
import { translate } from '../i18n/translate';
|
||||
|
||||
vi.mock('./chat', () => ({
|
||||
@@ -113,7 +114,7 @@ vi.mock('./common/KGDropdown', () => ({
|
||||
default: () => null,
|
||||
}));
|
||||
|
||||
function renderWithLocale(resolvedLocale: 'en_us' | 'zh_cn') {
|
||||
function renderWithLocale(resolvedLocale: ResolvedLocaleCode) {
|
||||
return render(
|
||||
<I18nContext.Provider
|
||||
value={{
|
||||
@@ -144,4 +145,10 @@ describe('ChatBox', () => {
|
||||
|
||||
expect(screen.getByRole('heading', { level: 3, name: 'K.G.Studio 音乐创作助手' })).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders the French assistant title under fr_fr', () => {
|
||||
renderWithLocale('fr_fr');
|
||||
|
||||
expect(screen.getByRole('heading', { level: 3, name: 'Assistant musical K.G.Studio' })).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
} from '../test/utils/mock-data';
|
||||
import { showAlert } from '../util/dialogUtil';
|
||||
import { I18nContext } from '../i18n/I18nProvider';
|
||||
import type { ResolvedLocaleCode } from '../i18n/types';
|
||||
import { translate } from '../i18n/translate';
|
||||
|
||||
const clickDropdownOption = (label: string) => {
|
||||
@@ -187,7 +188,7 @@ vi.mock('../core/KGCore', () => ({
|
||||
}));
|
||||
|
||||
describe('EventListPanel', () => {
|
||||
const renderWithLocale = (locale: 'en_us' | 'zh_cn' = 'en_us') => render(
|
||||
const renderWithLocale = (locale: ResolvedLocaleCode = 'en_us') => render(
|
||||
<I18nContext.Provider
|
||||
value={{
|
||||
languageSetting: locale,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import InstrumentSelection from './InstrumentSelection';
|
||||
import { KGMidiTrack } from '../core/track/KGMidiTrack';
|
||||
import { I18nContext } from '../i18n/I18nProvider';
|
||||
import type { ResolvedLocaleCode } from '../i18n/types';
|
||||
import { translate } from '../i18n/translate';
|
||||
|
||||
const midiTrack = new KGMidiTrack('Lead Track', 1, 'acoustic_grand_piano');
|
||||
@@ -19,7 +20,7 @@ vi.mock('../stores/projectStore', () => ({
|
||||
useProjectStore: () => storeState,
|
||||
}));
|
||||
|
||||
function renderWithLocale(resolvedLocale: 'en_us' | 'zh_cn') {
|
||||
function renderWithLocale(resolvedLocale: ResolvedLocaleCode) {
|
||||
return render(
|
||||
<I18nContext.Provider
|
||||
value={{
|
||||
|
||||
@@ -4,6 +4,7 @@ import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import PianoKeys from './PianoKeys';
|
||||
import { createMockMidiNote, createMockMidiRegion, createMockMidiTrack } from '../../test/utils/mock-data';
|
||||
import { I18nContext } from '../../i18n/I18nProvider';
|
||||
import type { ResolvedLocaleCode } from '../../i18n/types';
|
||||
import { translate } from '../../i18n/translate';
|
||||
|
||||
type TestLiveNoteActivityListener = (...args: [{ pitch: number; isNoteOn: boolean }]) => void;
|
||||
@@ -52,7 +53,7 @@ vi.mock('../../core/midi-input/KGMidiInput', () => ({
|
||||
|
||||
function renderWithLocale(
|
||||
ui: React.ReactElement,
|
||||
resolvedLocale: 'en_us' | 'zh_cn' = 'en_us',
|
||||
resolvedLocale: ResolvedLocaleCode = 'en_us',
|
||||
) {
|
||||
return render(
|
||||
<I18nContext.Provider
|
||||
|
||||
@@ -4,6 +4,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { KGPianoRollState, PIANO_ROLL_NO_SNAP } from '../../core/state/KGPianoRollState';
|
||||
import { createMockMidiControllerEvent, createMockMidiPitchBend, createMockMidiRegion, createMockMidiTrack } from '../../test/utils/mock-data';
|
||||
import { I18nContext } from '../../i18n/I18nProvider';
|
||||
import type { ResolvedLocaleCode } from '../../i18n/types';
|
||||
import { translate } from '../../i18n/translate';
|
||||
|
||||
const coreMock = {
|
||||
@@ -47,7 +48,7 @@ import PianoRollAutomationLane from './PianoRollAutomationLane';
|
||||
import { getControllerNumberForAutomationType } from './pianoRollAutomation';
|
||||
|
||||
describe('PianoRollAutomationLane', () => {
|
||||
const renderWithLocale = (ui: React.ReactElement, locale: 'en_us' | 'zh_cn' = 'en_us') => render(
|
||||
const renderWithLocale = (ui: React.ReactElement, locale: ResolvedLocaleCode = 'en_us') => render(
|
||||
<I18nContext.Provider
|
||||
value={{
|
||||
languageSetting: locale,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import PianoRollToolbar from './PianoRollToolbar';
|
||||
import { I18nContext } from '../../i18n/I18nProvider';
|
||||
import type { ResolvedLocaleCode } from '../../i18n/types';
|
||||
import { translate } from '../../i18n/translate';
|
||||
|
||||
vi.mock('../common', () => ({
|
||||
@@ -42,7 +43,7 @@ vi.mock('../../core/KGCore', () => ({
|
||||
}));
|
||||
|
||||
describe('PianoRollToolbar', () => {
|
||||
const renderWithLocale = (ui: React.ReactElement, locale: 'en_us' | 'zh_cn' = 'en_us') => render(
|
||||
const renderWithLocale = (ui: React.ReactElement, locale: ResolvedLocaleCode = 'en_us') => render(
|
||||
<I18nContext.Provider
|
||||
value={{
|
||||
languageSetting: locale,
|
||||
|
||||
@@ -89,15 +89,15 @@ vi.mock('../../../util/local-separator/modelCache', () => ({
|
||||
}));
|
||||
|
||||
describe('GeneralSettings', () => {
|
||||
const renderSettings = () => render(
|
||||
const renderSettings = (locale: 'en_us' | 'zh_cn' = 'en_us') => render(
|
||||
<I18nContext.Provider
|
||||
value={{
|
||||
languageSetting: 'auto',
|
||||
resolvedLocale: 'en_us',
|
||||
resolvedLocale: locale,
|
||||
setLanguageSetting: async (value) => {
|
||||
await configManagerMock.set('general.language', value);
|
||||
},
|
||||
t: (key, params) => translate(key, params, 'en_us'),
|
||||
t: (key, params) => translate(key, params, locale),
|
||||
}}
|
||||
>
|
||||
<GeneralSettings />
|
||||
@@ -137,6 +137,24 @@ describe('GeneralSettings', () => {
|
||||
expect(screen.getByText(/require more VRAM/i)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('uses native language names in the language dropdown', async () => {
|
||||
renderSettings();
|
||||
|
||||
const select = await screen.findByLabelText('Language');
|
||||
const options = Array.from((select as HTMLSelectElement).options).map((option) => option.text);
|
||||
|
||||
expect(options).toEqual(['Auto', 'English', 'Français', '简体中文', '繁體中文']);
|
||||
});
|
||||
|
||||
it('localizes the auto label while keeping language names native', async () => {
|
||||
renderSettings('zh_cn');
|
||||
|
||||
const select = await screen.findByLabelText('语言');
|
||||
const options = Array.from((select as HTMLSelectElement).options).map((option) => option.text);
|
||||
|
||||
expect(options).toEqual(['自动', 'English', 'Français', '简体中文', '繁體中文']);
|
||||
});
|
||||
|
||||
it('initializes the local context length from config', async () => {
|
||||
renderSettings();
|
||||
|
||||
@@ -251,11 +269,12 @@ describe('GeneralSettings', () => {
|
||||
const languageSelect = await screen.findByLabelText('Language');
|
||||
expect(languageSelect).toBeTruthy();
|
||||
expect(screen.getAllByRole('combobox')[0]).toBe(languageSelect);
|
||||
expect(screen.getByRole('option', { name: 'Français' })).toBeTruthy();
|
||||
|
||||
fireEvent.change(languageSelect, { target: { value: 'zh_cn' } });
|
||||
fireEvent.change(languageSelect, { target: { value: 'fr_fr' } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(configManagerMock.set).toHaveBeenCalledWith('general.language', 'zh_cn');
|
||||
expect(configManagerMock.set).toHaveBeenCalledWith('general.language', 'fr_fr');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,13 @@ import {
|
||||
LOCAL_SEPARATOR_MODEL_IDS,
|
||||
} from '../../../util/local-separator/config';
|
||||
|
||||
const LANGUAGE_OPTION_LABELS: Record<Exclude<LanguageSetting, 'auto'>, string> = {
|
||||
en_us: 'English',
|
||||
fr_fr: 'Français',
|
||||
zh_cn: '简体中文',
|
||||
zh_hk: '繁體中文',
|
||||
};
|
||||
|
||||
const GeneralSettings: React.FC = () => {
|
||||
const { t, setLanguageSetting } = useI18n();
|
||||
const [language, setLanguage] = useState<LanguageSetting>('auto');
|
||||
@@ -352,9 +359,10 @@ const GeneralSettings: React.FC = () => {
|
||||
onChange={(e) => void handleLanguageChange(e.target.value as LanguageSetting)}
|
||||
>
|
||||
<option value="auto">{t('settings.general.language.auto')}</option>
|
||||
<option value="en_us">{t('settings.general.language.en_us')}</option>
|
||||
<option value="zh_cn">{t('settings.general.language.zh_cn')}</option>
|
||||
<option value="zh_hk">{t('settings.general.language.zh_hk')}</option>
|
||||
<option value="en_us">{LANGUAGE_OPTION_LABELS.en_us}</option>
|
||||
<option value="fr_fr">{LANGUAGE_OPTION_LABELS.fr_fr}</option>
|
||||
<option value="zh_cn">{LANGUAGE_OPTION_LABELS.zh_cn}</option>
|
||||
<option value="zh_hk">{LANGUAGE_OPTION_LABELS.zh_hk}</option>
|
||||
</select>
|
||||
<div className="settings-help" style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
|
||||
{t('settings.general.language.help')}
|
||||
|
||||
Reference in New Issue
Block a user