mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-05 07:10:55 +00:00
Auto-detect LLM output language from system locale on first startup
This commit is contained in:
parent
a92be72e88
commit
4d9f25e9fe
5 changed files with 259 additions and 1 deletions
|
|
@ -15,6 +15,7 @@ import { type LoadedSettings, SettingScope } from '../config/settings.js';
|
|||
import { performInitialAuth } from './auth.js';
|
||||
import { validateTheme } from './theme.js';
|
||||
import { initializeI18n } from '../i18n/index.js';
|
||||
import { initializeLlmOutputLanguage } from '../ui/commands/languageCommand.js';
|
||||
|
||||
export interface InitializationResult {
|
||||
authError: string | null;
|
||||
|
|
@ -41,6 +42,9 @@ export async function initializeApp(
|
|||
'auto';
|
||||
await initializeI18n(languageSetting);
|
||||
|
||||
// Auto-detect and set LLM output language on first use
|
||||
initializeLlmOutputLanguage();
|
||||
|
||||
const authType = settings.merged.security?.auth?.selectedType;
|
||||
const authError = await performInitialAuth(config, authType);
|
||||
|
||||
|
|
|
|||
|
|
@ -46,17 +46,27 @@ const getLocalePath = (
|
|||
return path.join(baseDir, `${lang}.js`);
|
||||
};
|
||||
|
||||
// Supported locale codes mapped to English language names
|
||||
const LOCALE_TO_LANGUAGE_NAME: Record<string, string> = {
|
||||
zh: 'Chinese',
|
||||
en: 'English',
|
||||
ru: 'Russian',
|
||||
de: 'German',
|
||||
};
|
||||
|
||||
// Language detection
|
||||
export function detectSystemLanguage(): SupportedLanguage {
|
||||
const envLang = process.env['QWEN_CODE_LANG'] || process.env['LANG'];
|
||||
if (envLang?.startsWith('zh')) return 'zh';
|
||||
if (envLang?.startsWith('en')) return 'en';
|
||||
if (envLang?.startsWith('ru')) return 'ru';
|
||||
if (envLang?.startsWith('de')) return 'de';
|
||||
|
||||
try {
|
||||
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
||||
if (locale.startsWith('zh')) return 'zh';
|
||||
if (locale.startsWith('ru')) return 'ru';
|
||||
if (locale.startsWith('de')) return 'de';
|
||||
} catch {
|
||||
// Fallback to default
|
||||
}
|
||||
|
|
@ -64,6 +74,14 @@ export function detectSystemLanguage(): SupportedLanguage {
|
|||
return 'en';
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a locale code to its English language name.
|
||||
* Used for LLM output language instructions.
|
||||
*/
|
||||
export function getLanguageNameFromLocale(locale: SupportedLanguage): string {
|
||||
return LOCALE_TO_LANGUAGE_NAME[locale] || 'English';
|
||||
}
|
||||
|
||||
// Translation loading
|
||||
async function loadTranslationsAsync(
|
||||
lang: SupportedLanguage,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,16 @@ import { createMockCommandContext } from '../../test-utils/mockCommandContext.js
|
|||
vi.mock('../../i18n/index.js', () => ({
|
||||
setLanguageAsync: vi.fn().mockResolvedValue(undefined),
|
||||
getCurrentLanguage: vi.fn().mockReturnValue('en'),
|
||||
detectSystemLanguage: vi.fn().mockReturnValue('en'),
|
||||
getLanguageNameFromLocale: vi.fn((locale: string) => {
|
||||
const map: Record<string, string> = {
|
||||
zh: 'Chinese',
|
||||
en: 'English',
|
||||
ru: 'Russian',
|
||||
de: 'German',
|
||||
};
|
||||
return map[locale] || 'English';
|
||||
}),
|
||||
t: vi.fn((key: string) => key),
|
||||
}));
|
||||
|
||||
|
|
@ -61,7 +71,10 @@ vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {
|
|||
|
||||
// Import modules after mocking
|
||||
import * as i18n from '../../i18n/index.js';
|
||||
import { languageCommand } from './languageCommand.js';
|
||||
import {
|
||||
languageCommand,
|
||||
initializeLlmOutputLanguage,
|
||||
} from './languageCommand.js';
|
||||
|
||||
describe('languageCommand', () => {
|
||||
let mockContext: CommandContext;
|
||||
|
|
@ -597,4 +610,74 @@ describe('languageCommand', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializeLlmOutputLanguage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
vi.mocked(fs.mkdirSync).mockImplementation(() => undefined);
|
||||
vi.mocked(fs.writeFileSync).mockImplementation(() => undefined);
|
||||
});
|
||||
|
||||
it('should create file when it does not exist', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
vi.mocked(i18n.detectSystemLanguage).mockReturnValue('en');
|
||||
|
||||
initializeLlmOutputLanguage();
|
||||
|
||||
expect(fs.mkdirSync).toHaveBeenCalled();
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
expect.stringContaining('output-language.md'),
|
||||
expect.stringContaining('English'),
|
||||
'utf-8',
|
||||
);
|
||||
});
|
||||
|
||||
it('should NOT overwrite existing file', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
|
||||
initializeLlmOutputLanguage();
|
||||
|
||||
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should detect Chinese locale and create Chinese rule file', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
vi.mocked(i18n.detectSystemLanguage).mockReturnValue('zh');
|
||||
|
||||
initializeLlmOutputLanguage();
|
||||
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
expect.stringContaining('output-language.md'),
|
||||
expect.stringContaining('Chinese'),
|
||||
'utf-8',
|
||||
);
|
||||
});
|
||||
|
||||
it('should detect Russian locale and create Russian rule file', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
vi.mocked(i18n.detectSystemLanguage).mockReturnValue('ru');
|
||||
|
||||
initializeLlmOutputLanguage();
|
||||
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
expect.stringContaining('output-language.md'),
|
||||
expect.stringContaining('Russian'),
|
||||
'utf-8',
|
||||
);
|
||||
});
|
||||
|
||||
it('should detect German locale and create German rule file', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
vi.mocked(i18n.detectSystemLanguage).mockReturnValue('de');
|
||||
|
||||
initializeLlmOutputLanguage();
|
||||
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
expect.stringContaining('output-language.md'),
|
||||
expect.stringContaining('German'),
|
||||
'utf-8',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import { SettingScope } from '../../config/settings.js';
|
|||
import {
|
||||
setLanguageAsync,
|
||||
getCurrentLanguage,
|
||||
detectSystemLanguage,
|
||||
getLanguageNameFromLocale,
|
||||
type SupportedLanguage,
|
||||
t,
|
||||
} from '../../i18n/index.js';
|
||||
|
|
@ -73,6 +75,33 @@ function getLlmOutputLanguageRulePath(): string {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the LLM output language rule file on first startup.
|
||||
* If the file already exists, it is not overwritten (respects user preference).
|
||||
*/
|
||||
export function initializeLlmOutputLanguage(): void {
|
||||
const filePath = getLlmOutputLanguageRulePath();
|
||||
|
||||
// Skip if file already exists (user preference)
|
||||
if (fs.existsSync(filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect system language and map to language name
|
||||
const detectedLocale = detectSystemLanguage();
|
||||
const languageName = getLanguageNameFromLocale(detectedLocale);
|
||||
|
||||
// Generate the rule file
|
||||
const content = generateLlmOutputLanguageRule(languageName);
|
||||
|
||||
// Ensure directory exists
|
||||
const dir = path.dirname(filePath);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
|
||||
// Write file
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current LLM output language from the rule file if it exists.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue