mirror of
https://github.com/mssoftjp/obsidian-voice-input.git
synced 2026-07-22 06:44:48 +00:00
refactor(language): remove 'auto' recognition language\n\n- Drop 'auto' from settings types and UI options\n- Migrate existing 'auto' values to detected locale\n- Always send explicit language to API\n- Update tests and copy\n- Adjust defaults and first-run init
This commit is contained in:
parent
4ed761d152
commit
19d7924aab
6 changed files with 61 additions and 75 deletions
|
|
@ -98,13 +98,11 @@ export class TranscriptionService implements ITranscriptionProvider {
|
|||
formData.append('response_format', 'json');
|
||||
formData.append('temperature', String(API_CONSTANTS.PARAMETERS.TRANSCRIPTION_TEMPERATURE)); // Deterministic output
|
||||
|
||||
// Language setting
|
||||
if (language !== 'auto') {
|
||||
formData.append('language', language);
|
||||
}
|
||||
// Language setting (auto 廃止のため常に明示指定)
|
||||
formData.append('language', language);
|
||||
|
||||
const prompt = this.buildTranscriptionPrompt(language);
|
||||
if (language !== 'auto' && prompt) {
|
||||
if (prompt) {
|
||||
formData.append('prompt', prompt);
|
||||
}
|
||||
|
||||
|
|
@ -229,10 +227,7 @@ export class TranscriptionService implements ITranscriptionProvider {
|
|||
* Build prompt for GPT-4o transcription (for all languages except auto)
|
||||
*/
|
||||
private buildTranscriptionPrompt(language: string): string {
|
||||
// No prompt for auto language mode (as it might interfere with language detection)
|
||||
if (language === 'auto') {
|
||||
return '';
|
||||
}
|
||||
// auto は廃止済み
|
||||
|
||||
const normalizedLang = this.normalizeLanguage(language);
|
||||
|
||||
|
|
@ -361,7 +356,7 @@ ${PROMPT_CONSTANTS.KOREAN.SPEAKER_ONLY}
|
|||
* Normalize language code for consistent processing
|
||||
*/
|
||||
private normalizeLanguage(language: string): string {
|
||||
if (language === 'auto') return 'auto';
|
||||
// auto は廃止済み
|
||||
const lang = language.toLowerCase();
|
||||
if (lang.startsWith('ja')) return 'ja';
|
||||
if (lang.startsWith('zh')) return 'zh';
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ export const en: TranslationResource = {
|
|||
transcriptionLanguage: 'Transcription Language',
|
||||
transcriptionLanguageDesc: 'Language for voice recognition and transcription.',
|
||||
pluginLanguage: 'Plugin Language',
|
||||
pluginLanguageDesc: 'Set language for UI display (voice recognition language is auto-detected separately)',
|
||||
pluginLanguageDesc: 'Set language for UI display',
|
||||
// Advanced settings
|
||||
languageLinking: 'Link UI and recognition languages',
|
||||
languageLinkingDesc: 'When enabled, recognition language follows UI language. When disabled, you can set recognition language independently.',
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export interface VoiceInputSettings {
|
|||
// 録音設定
|
||||
maxRecordingSeconds: number; // 最大録音時間(秒)
|
||||
// 言語設定
|
||||
transcriptionLanguage: 'auto' | 'ja' | 'en' | 'zh' | 'ko'; // 音声認識言語(後方互換性のため維持)
|
||||
transcriptionLanguage: 'ja' | 'en' | 'zh' | 'ko'; // 音声認識言語
|
||||
pluginLanguage: Locale; // プラグインUI表示の言語
|
||||
customDictionary: SimpleCorrectionDictionary;
|
||||
// デバッグ設定
|
||||
|
|
@ -18,7 +18,7 @@ export interface VoiceInputSettings {
|
|||
// 高度設定
|
||||
advanced: {
|
||||
languageLinkingEnabled: boolean; // UI言語と認識言語を連動する(デフォルト: true)
|
||||
transcriptionLanguage?: 'auto' | 'ja' | 'en' | 'zh' | 'ko'; // 独立した音声認識言語設定
|
||||
transcriptionLanguage?: 'ja' | 'en' | 'zh' | 'ko'; // 独立した音声認識言語設定
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ export const DEFAULT_SETTINGS: VoiceInputSettings = {
|
|||
// 録音設定
|
||||
maxRecordingSeconds: 300, // 5分(300秒)
|
||||
// 言語設定
|
||||
transcriptionLanguage: 'auto', // 音声認識言語のデフォルトは自動検出(後方互換性のため維持)
|
||||
transcriptionLanguage: 'en', // 初期値(実際は起動時に環境ロケールへ移行)
|
||||
pluginLanguage: 'en', // 初期値、実際はObsidianの設定に従う
|
||||
customDictionary: { definiteCorrections: [] },
|
||||
// デバッグ設定
|
||||
|
|
@ -37,7 +37,7 @@ export const DEFAULT_SETTINGS: VoiceInputSettings = {
|
|||
logLevel: LogLevel.INFO, // 通常レベル
|
||||
// 高度設定
|
||||
advanced: {
|
||||
languageLinkingEnabled: true, // デフォルトは連動オン(現行動作維持)
|
||||
transcriptionLanguage: 'auto' // デフォルトは自動検出
|
||||
languageLinkingEnabled: true,
|
||||
transcriptionLanguage: 'en'
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -221,14 +221,17 @@ export default class VoiceInputPlugin extends Plugin {
|
|||
this.logger?.info('Migrating interfaceLanguage to pluginLanguage');
|
||||
}
|
||||
|
||||
// languageからtranscriptionLanguageへの移行
|
||||
// languageからtranscriptionLanguageへの移行(autoは検出ロケールへ変換)
|
||||
if ('language' in data && !('transcriptionLanguage' in data)) {
|
||||
// 既存のlanguageフィールドをtranscriptionLanguageに移行
|
||||
const langValue = data.language;
|
||||
if (langValue === 'auto' || langValue === 'ja' || langValue === 'en' || langValue === 'zh' || langValue === 'ko') {
|
||||
if (langValue === 'ja' || langValue === 'en' || langValue === 'zh' || langValue === 'ko') {
|
||||
migratedData.transcriptionLanguage = langValue;
|
||||
} else if (langValue === 'auto') {
|
||||
// auto は廃止: 起動環境のロケールへ固定
|
||||
migratedData.transcriptionLanguage = this.detectPluginLanguage();
|
||||
} else {
|
||||
migratedData.transcriptionLanguage = 'auto';
|
||||
migratedData.transcriptionLanguage = this.detectPluginLanguage();
|
||||
}
|
||||
delete migratedData.language;
|
||||
needsSave = true;
|
||||
|
|
@ -322,12 +325,12 @@ export default class VoiceInputPlugin extends Plugin {
|
|||
this.logger?.info(`Auto-detected language: ${this.settings.pluginLanguage} (from Obsidian: ${getObsidianLocale(this.app)})`);
|
||||
}
|
||||
|
||||
// 高度設定のマイグレーション
|
||||
// 高度設定のマイグレーション(auto を廃止)
|
||||
if (!hasSettingsKey(data, 'advanced')) {
|
||||
// 既存ユーザーには言語連動をデフォルトで有効化(現行動作維持)
|
||||
this.settings.advanced = {
|
||||
languageLinkingEnabled: true,
|
||||
transcriptionLanguage: 'auto'
|
||||
transcriptionLanguage: this.detectPluginLanguage()
|
||||
};
|
||||
needsSave = true;
|
||||
this.logger?.info('Initialized advanced settings with language linking enabled for backward compatibility');
|
||||
|
|
@ -336,17 +339,25 @@ export default class VoiceInputPlugin extends Plugin {
|
|||
this.settings.advanced.languageLinkingEnabled = true;
|
||||
needsSave = true;
|
||||
this.logger?.info('Added languageLinkingEnabled to existing advanced settings');
|
||||
} else if (data.advanced) {
|
||||
// auto からの置換
|
||||
const adv = data.advanced as { transcriptionLanguage?: string };
|
||||
if (adv.transcriptionLanguage === 'auto') {
|
||||
this.settings.advanced.transcriptionLanguage = this.detectPluginLanguage();
|
||||
needsSave = true;
|
||||
this.logger?.info('Migrated advanced.transcriptionLanguage from auto to detected locale');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 保存データが存在しない場合(初回起動)
|
||||
this.settings.pluginLanguage = this.detectPluginLanguage();
|
||||
this.settings.transcriptionLanguage = 'auto';
|
||||
this.settings.transcriptionLanguage = this.detectPluginLanguage();
|
||||
this.settings.advanced = {
|
||||
languageLinkingEnabled: true,
|
||||
transcriptionLanguage: 'auto'
|
||||
transcriptionLanguage: this.detectPluginLanguage()
|
||||
};
|
||||
needsSave = true;
|
||||
this.logger?.info(`First run - auto-detected language: ${this.settings.pluginLanguage}, transcriptionLanguage: auto, advanced settings initialized`);
|
||||
this.logger?.info(`First run - detected locale: ${this.settings.pluginLanguage}, transcriptionLanguage set to detected locale, advanced settings initialized`);
|
||||
}
|
||||
|
||||
// 必要に応じて設定を保存
|
||||
|
|
@ -382,19 +393,17 @@ export default class VoiceInputPlugin extends Plugin {
|
|||
|
||||
/**
|
||||
* 解決済み言語を取得(高度設定の連動設定に基づく)
|
||||
* 仕様変更: 'auto' の場合は API に 'auto' をそのまま渡す
|
||||
* auto は廃止済みのため、常に具体的な言語コードを返す
|
||||
*/
|
||||
getResolvedLanguage(): 'auto' | 'ja' | 'zh' | 'ko' | 'en' {
|
||||
getResolvedLanguage(): 'ja' | 'zh' | 'ko' | 'en' {
|
||||
// 言語連動が無効な場合: advanced.transcriptionLanguage を優先
|
||||
if (this.settings.advanced?.languageLinkingEnabled === false) {
|
||||
const advancedLang = this.settings.advanced.transcriptionLanguage ?? 'auto';
|
||||
return advancedLang as 'auto' | 'ja' | 'zh' | 'ko' | 'en';
|
||||
const advancedLang = this.settings.advanced?.transcriptionLanguage;
|
||||
return (advancedLang ?? this.detectPluginLanguage()) as 'ja' | 'zh' | 'ko' | 'en';
|
||||
}
|
||||
|
||||
// 言語連動が有効(デフォルト)の場合: 通常の transcriptionLanguage を使用
|
||||
// transcriptionLanguage が 'auto' なら 'auto' のまま返す
|
||||
const baseLang = this.settings.transcriptionLanguage ?? 'auto';
|
||||
return baseLang as 'auto' | 'ja' | 'zh' | 'ko' | 'en';
|
||||
// 言語連動が有効: 通常の transcriptionLanguage を使用
|
||||
const baseLang = this.settings.transcriptionLanguage;
|
||||
return (baseLang ?? this.detectPluginLanguage()) as 'ja' | 'zh' | 'ko' | 'en';
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export class VoiceInputSettingTab extends PluginSettingTab {
|
|||
if (!this.plugin.settings.advanced) {
|
||||
this.plugin.settings.advanced = {
|
||||
languageLinkingEnabled: value,
|
||||
transcriptionLanguage: 'auto'
|
||||
transcriptionLanguage: this.plugin.getResolvedLanguage()
|
||||
};
|
||||
} else {
|
||||
this.plugin.settings.advanced.languageLinkingEnabled = value;
|
||||
|
|
@ -100,13 +100,12 @@ export class VoiceInputSettingTab extends PluginSettingTab {
|
|||
.setName(this.i18n.t('ui.settings.advancedTranscriptionLanguage'))
|
||||
.setDesc(this.i18n.t('ui.settings.advancedTranscriptionLanguageDesc'))
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('auto', this.i18n.t('ui.options.languageAuto'))
|
||||
.addOption('ja', this.i18n.t('ui.options.languageJa'))
|
||||
.addOption('en', this.i18n.t('ui.options.languageEn'))
|
||||
.addOption('zh', this.i18n.t('ui.options.languageZh'))
|
||||
.addOption('ko', this.i18n.t('ui.options.languageKo'))
|
||||
.setValue(this.plugin.settings.advanced.transcriptionLanguage ?? 'auto')
|
||||
.onChange(async (value: 'auto' | 'ja' | 'en' | 'zh' | 'ko') => {
|
||||
.setValue(this.plugin.settings.advanced.transcriptionLanguage ?? this.plugin.getResolvedLanguage())
|
||||
.onChange(async (value: 'ja' | 'en' | 'zh' | 'ko') => {
|
||||
if (!this.plugin.settings.advanced) {
|
||||
this.plugin.settings.advanced = {
|
||||
languageLinkingEnabled: false,
|
||||
|
|
|
|||
|
|
@ -21,19 +21,12 @@ function detectPluginLanguage(getObsidianLocaleFn: () => string): 'ja' | 'zh' |
|
|||
}
|
||||
|
||||
function getResolvedLanguage(
|
||||
pluginLanguage: string,
|
||||
pluginLanguage: string,
|
||||
detectFn: () => 'ja' | 'zh' | 'ko' | 'en'
|
||||
): 'auto' | 'ja' | 'zh' | 'ko' | 'en' {
|
||||
if (pluginLanguage === 'auto') {
|
||||
// 仕様変更: 'auto' はそのまま返す(API に渡す)
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
): 'ja' | 'zh' | 'ko' | 'en' {
|
||||
if (!['ja', 'zh', 'ko', 'en'].includes(pluginLanguage)) {
|
||||
// 不正値は従来通り検出へフォールバック
|
||||
return detectFn();
|
||||
}
|
||||
|
||||
return pluginLanguage as 'ja' | 'zh' | 'ko' | 'en';
|
||||
}
|
||||
|
||||
|
|
@ -99,11 +92,7 @@ describe('Language Detection Logic', () => {
|
|||
expect(mockDetectFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should handle auto value (pass-through)', () => {
|
||||
const result = getResolvedLanguage('auto', mockDetectFn);
|
||||
expect(result).toBe('auto');
|
||||
expect(mockDetectFn).not.toHaveBeenCalled();
|
||||
});
|
||||
// auto は廃止済みのためテストしない
|
||||
|
||||
test('should work with all supported languages', () => {
|
||||
expect(getResolvedLanguage('ja', mockDetectFn)).toBe('ja');
|
||||
|
|
@ -123,24 +112,18 @@ describe('Language Detection Logic', () => {
|
|||
|
||||
// Function to simulate the new advanced language resolution logic
|
||||
function getResolvedLanguageAdvanced(
|
||||
transcriptionLanguage: string,
|
||||
transcriptionLanguage: string | undefined,
|
||||
advanced: { languageLinkingEnabled?: boolean; transcriptionLanguage?: string } | undefined,
|
||||
detectFn: () => 'ja' | 'zh' | 'ko' | 'en'
|
||||
): 'auto' | 'ja' | 'zh' | 'ko' | 'en' {
|
||||
// 高度設定で言語連動が無効な場合: advanced.transcriptionLanguage を優先
|
||||
): 'ja' | 'zh' | 'ko' | 'en' {
|
||||
if (advanced?.languageLinkingEnabled === false) {
|
||||
const advancedLang = advanced.transcriptionLanguage ?? 'auto';
|
||||
return (advancedLang as 'auto' | 'ja' | 'zh' | 'ko' | 'en');
|
||||
const adv = advanced?.transcriptionLanguage;
|
||||
return (['ja', 'zh', 'ko', 'en'].includes(adv as any) ? adv : detectFn()) as 'ja' | 'zh' | 'ko' | 'en';
|
||||
}
|
||||
|
||||
// 言語連動が有効な場合(デフォルト): 通常の transcriptionLanguage を使用
|
||||
if (transcriptionLanguage === 'auto') {
|
||||
return 'auto';
|
||||
}
|
||||
return transcriptionLanguage as 'ja' | 'zh' | 'ko' | 'en';
|
||||
return (['ja', 'zh', 'ko', 'en'].includes(transcriptionLanguage as any) ? transcriptionLanguage : detectFn()) as 'ja' | 'zh' | 'ko' | 'en';
|
||||
}
|
||||
|
||||
test('should return auto when linking is enabled and TL is auto', () => {
|
||||
test('should use TL when linking enabled; fallback to detection when missing/invalid', () => {
|
||||
// Default case: advanced.languageLinkingEnabled is true
|
||||
const advanced = { languageLinkingEnabled: true };
|
||||
|
||||
|
|
@ -148,9 +131,9 @@ describe('Language Detection Logic', () => {
|
|||
expect(getResolvedLanguageAdvanced('ja', advanced, mockDetectFn)).toBe('ja');
|
||||
expect(mockDetectFn).not.toHaveBeenCalled();
|
||||
|
||||
// Should pass through 'auto'
|
||||
expect(getResolvedLanguageAdvanced('auto', advanced, mockDetectFn)).toBe('auto');
|
||||
expect(mockDetectFn).not.toHaveBeenCalled();
|
||||
// Missing or invalid TL falls back to detection
|
||||
expect(getResolvedLanguageAdvanced(undefined, advanced, mockDetectFn)).toBe('ko');
|
||||
expect(getResolvedLanguageAdvanced('invalid', advanced, mockDetectFn)).toBe('ko');
|
||||
});
|
||||
|
||||
test('should treat undefined advanced as linking enabled', () => {
|
||||
|
|
@ -158,8 +141,8 @@ describe('Language Detection Logic', () => {
|
|||
expect(getResolvedLanguageAdvanced('ja', undefined, mockDetectFn)).toBe('ja');
|
||||
expect(mockDetectFn).not.toHaveBeenCalled();
|
||||
|
||||
expect(getResolvedLanguageAdvanced('auto', undefined, mockDetectFn)).toBe('auto');
|
||||
expect(mockDetectFn).not.toHaveBeenCalled();
|
||||
expect(getResolvedLanguageAdvanced(undefined, undefined, mockDetectFn)).toBe('ko');
|
||||
expect(mockDetectFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should use advanced.transcriptionLanguage when language linking is disabled', () => {
|
||||
|
|
@ -173,24 +156,24 @@ describe('Language Detection Logic', () => {
|
|||
expect(mockDetectFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should pass through auto when linking disabled and advanced TL is auto', () => {
|
||||
test('should use advanced TL when linking disabled; fallback to detection when missing/invalid', () => {
|
||||
const advanced = {
|
||||
languageLinkingEnabled: false,
|
||||
transcriptionLanguage: 'auto'
|
||||
transcriptionLanguage: undefined as unknown as string
|
||||
};
|
||||
|
||||
expect(getResolvedLanguageAdvanced('ja', advanced, mockDetectFn)).toBe('auto');
|
||||
expect(mockDetectFn).not.toHaveBeenCalled();
|
||||
expect(getResolvedLanguageAdvanced('ja', advanced, mockDetectFn)).toBe('ko');
|
||||
expect(mockDetectFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should pass through auto when linking disabled and advanced TL is undefined', () => {
|
||||
test('should use detection when linking disabled and advanced TL is undefined', () => {
|
||||
const advanced = {
|
||||
languageLinkingEnabled: false
|
||||
// transcriptionLanguage is undefined
|
||||
};
|
||||
|
||||
expect(getResolvedLanguageAdvanced('ja', advanced, mockDetectFn)).toBe('auto');
|
||||
expect(mockDetectFn).not.toHaveBeenCalled();
|
||||
expect(getResolvedLanguageAdvanced('ja', advanced, mockDetectFn)).toBe('ko');
|
||||
expect(mockDetectFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should work with all supported languages in advanced mode', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue