feat(ui): add spell check support for text inputs (#1304)
* feat: add spell check with context menu and language sync Enables spell checking in text inputs with: - Right-click context menu with spelling suggestions - "Add to Dictionary" option (localized for en/fr) - Standard editing options (cut/copy/paste/select all) - Spell check language syncs with i18n app language - Input/Textarea components default spellCheck=true and lang attribute Files: - app-language.ts: Tracks app language for context menu labels - spellcheck.ts: Language mapping and localized labels - index.ts: Context menu handler with spell check integration - settings-handlers.ts: IPC handler for language switching - App.tsx: Syncs spell check language with i18n changes Supersedes PR #1304 (rebased from conflicting branch) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use i18n.language in useEffect dependency instead of i18n object The i18n object reference from react-i18next can change on every render even when the language hasn't changed, causing the effect to fire more frequently than necessary and making unnecessary IPC calls. Changed dependency from [settings.language, i18n] to [settings.language, i18n.language] to only re-run when the actual language changes. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: call initAppLanguage() on startup for immediate locale sync initAppLanguage() was defined but never called, leaving the main process language hardcoded to 'en' until the renderer sent the first IPC sync. Now called in app.whenReady() so context menu labels (e.g. "Add to Dictionary") are localized immediately from the OS locale. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* App language tracking module for main process.
|
||||
*
|
||||
* Tracks the user's in-app language setting (not OS locale) for use in
|
||||
* main process code that needs localized strings (e.g., context menus).
|
||||
*
|
||||
* Updated via IPC when user changes language in settings.
|
||||
*/
|
||||
|
||||
import { app } from 'electron';
|
||||
|
||||
// Current app language, defaults to 'en'
|
||||
// Updated via setAppLanguage() when renderer notifies of language change
|
||||
let currentAppLanguage = 'en';
|
||||
|
||||
/**
|
||||
* Get the current app language.
|
||||
* Falls back to 'en' if not set.
|
||||
*/
|
||||
export function getAppLanguage(): string {
|
||||
return currentAppLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current app language.
|
||||
* Called by IPC handler when renderer changes language.
|
||||
*/
|
||||
export function setAppLanguage(language: string): void {
|
||||
currentAppLanguage = language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize app language from OS locale as a starting point.
|
||||
* The renderer will update this once i18n initializes.
|
||||
*/
|
||||
export function initAppLanguage(): void {
|
||||
try {
|
||||
// app.getLocale() may not be available in test environments
|
||||
const osLocale = app?.getLocale?.() || 'en';
|
||||
// Extract base language (e.g., 'en-US' -> 'en')
|
||||
currentAppLanguage = osLocale.split('-')[0] || 'en';
|
||||
} catch {
|
||||
currentAppLanguage = 'en';
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ for (const envPath of possibleEnvPaths) {
|
||||
}
|
||||
}
|
||||
|
||||
import { app, BrowserWindow, shell, nativeImage, session, screen } from 'electron';
|
||||
import { app, BrowserWindow, shell, nativeImage, session, screen, Menu, MenuItem } from 'electron';
|
||||
import { join } from 'path';
|
||||
import { accessSync, readFileSync, writeFileSync, rmSync } from 'fs';
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils';
|
||||
@@ -46,7 +46,8 @@ import { pythonEnvManager } from './python-env-manager';
|
||||
import { getUsageMonitor } from './claude-profile/usage-monitor';
|
||||
import { initializeUsageMonitorForwarding } from './ipc-handlers/terminal-handlers';
|
||||
import { initializeAppUpdater, stopPeriodicUpdates } from './app-updater';
|
||||
import { DEFAULT_APP_SETTINGS, IPC_CHANNELS } from '../shared/constants';
|
||||
import { DEFAULT_APP_SETTINGS, IPC_CHANNELS, SPELL_CHECK_LANGUAGE_MAP, DEFAULT_SPELL_CHECK_LANGUAGE, ADD_TO_DICTIONARY_LABELS } from '../shared/constants';
|
||||
import { getAppLanguage, initAppLanguage } from './app-language';
|
||||
import { readSettingsFile } from './settings-utils';
|
||||
import { setupErrorLogging } from './app-logger';
|
||||
import { initSentryMain } from './sentry';
|
||||
@@ -204,7 +205,8 @@ function createWindow(): void {
|
||||
sandbox: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
backgroundThrottling: false // Prevent terminal lag when window loses focus
|
||||
backgroundThrottling: false, // Prevent terminal lag when window loses focus
|
||||
spellcheck: true // Enable spell check for text inputs
|
||||
}
|
||||
});
|
||||
|
||||
@@ -213,6 +215,87 @@ function createWindow(): void {
|
||||
mainWindow?.show();
|
||||
});
|
||||
|
||||
// Configure initial spell check languages with proper fallback logic
|
||||
// Uses shared constant for consistency with the IPC handler
|
||||
const defaultLanguage = 'en';
|
||||
const defaultSpellCheckLanguages = SPELL_CHECK_LANGUAGE_MAP[defaultLanguage] || [DEFAULT_SPELL_CHECK_LANGUAGE];
|
||||
const availableSpellCheckLanguages = session.defaultSession.availableSpellCheckerLanguages;
|
||||
const validSpellCheckLanguages = defaultSpellCheckLanguages.filter(lang =>
|
||||
availableSpellCheckLanguages.includes(lang)
|
||||
);
|
||||
const initialSpellCheckLanguages = validSpellCheckLanguages.length > 0
|
||||
? validSpellCheckLanguages
|
||||
: (availableSpellCheckLanguages.includes(DEFAULT_SPELL_CHECK_LANGUAGE) ? [DEFAULT_SPELL_CHECK_LANGUAGE] : []);
|
||||
|
||||
if (initialSpellCheckLanguages.length > 0) {
|
||||
session.defaultSession.setSpellCheckerLanguages(initialSpellCheckLanguages);
|
||||
console.log(`[SPELLCHECK] Initial languages set to: ${initialSpellCheckLanguages.join(', ')}`);
|
||||
} else {
|
||||
console.warn('[SPELLCHECK] No spell check languages available on this system');
|
||||
}
|
||||
|
||||
// Handle context menu with spell check and standard editing options
|
||||
mainWindow.webContents.on('context-menu', (_event, params) => {
|
||||
const menu = new Menu();
|
||||
|
||||
// Add spelling suggestions if there's a misspelled word
|
||||
if (params.misspelledWord) {
|
||||
for (const suggestion of params.dictionarySuggestions) {
|
||||
menu.append(new MenuItem({
|
||||
label: suggestion,
|
||||
click: () => mainWindow?.webContents.replaceMisspelling(suggestion)
|
||||
}));
|
||||
}
|
||||
|
||||
if (params.dictionarySuggestions.length > 0) {
|
||||
menu.append(new MenuItem({ type: 'separator' }));
|
||||
}
|
||||
|
||||
// Use localized label for "Add to Dictionary" based on app language (not OS locale)
|
||||
// getAppLanguage() tracks the user's in-app language setting, updated via SPELLCHECK_SET_LANGUAGES IPC
|
||||
const addToDictionaryLabel = ADD_TO_DICTIONARY_LABELS[getAppLanguage()] || ADD_TO_DICTIONARY_LABELS['en'];
|
||||
menu.append(new MenuItem({
|
||||
label: addToDictionaryLabel,
|
||||
click: () => mainWindow?.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord)
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({ type: 'separator' }));
|
||||
}
|
||||
|
||||
// Standard editing options for editable fields
|
||||
// Using role without explicit label allows Electron to provide localized labels
|
||||
if (params.isEditable) {
|
||||
menu.append(new MenuItem({
|
||||
role: 'cut',
|
||||
enabled: params.editFlags.canCut
|
||||
}));
|
||||
menu.append(new MenuItem({
|
||||
role: 'copy',
|
||||
enabled: params.editFlags.canCopy
|
||||
}));
|
||||
menu.append(new MenuItem({
|
||||
role: 'paste',
|
||||
enabled: params.editFlags.canPaste
|
||||
}));
|
||||
menu.append(new MenuItem({
|
||||
role: 'selectAll',
|
||||
enabled: params.editFlags.canSelectAll
|
||||
}));
|
||||
} else if (params.selectionText?.trim()) {
|
||||
// Non-editable text selection (e.g., labels, paragraphs)
|
||||
// Use .trim() to avoid showing menu for whitespace-only selections
|
||||
menu.append(new MenuItem({
|
||||
role: 'copy',
|
||||
enabled: params.editFlags.canCopy
|
||||
}));
|
||||
}
|
||||
|
||||
// Only show menu if there are items
|
||||
if (menu.items.length > 0) {
|
||||
menu.popup();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle external links with URL scheme allowlist for security
|
||||
// Note: Terminal links now use IPC via WebLinksAddon callback, but this handler
|
||||
// catches any other window.open() calls (e.g., from third-party libraries)
|
||||
@@ -278,6 +361,9 @@ app.whenReady().then(() => {
|
||||
.catch((err) => console.warn('[main] Failed to clear cache:', err));
|
||||
}
|
||||
|
||||
// Initialize app language from OS locale for main process i18n (context menus)
|
||||
initAppLanguage();
|
||||
|
||||
// Clean up stale update metadata from the old source updater system
|
||||
// This prevents version display desync after electron-updater installs a new version
|
||||
cleanupStaleUpdateMetadata();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ipcMain, dialog, app, shell } from 'electron';
|
||||
import { ipcMain, dialog, app, shell, session } from 'electron';
|
||||
import { existsSync, writeFileSync, mkdirSync, statSync, readFileSync } from 'fs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import path from 'path';
|
||||
@@ -8,7 +8,8 @@ import { is } from '@electron-toolkit/utils';
|
||||
// ESM-compatible __dirname
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
import { IPC_CHANNELS, DEFAULT_APP_SETTINGS, DEFAULT_AGENT_PROFILES } from '../../shared/constants';
|
||||
import { IPC_CHANNELS, DEFAULT_APP_SETTINGS, DEFAULT_AGENT_PROFILES, SPELL_CHECK_LANGUAGE_MAP, DEFAULT_SPELL_CHECK_LANGUAGE } from '../../shared/constants';
|
||||
import { setAppLanguage } from '../app-language';
|
||||
import type {
|
||||
AppSettings,
|
||||
IPCResult,
|
||||
@@ -801,4 +802,64 @@ export function registerSettingsHandlers(
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Spell Check Operations
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Set spell check languages based on app language.
|
||||
* Called when renderer's i18n language changes to sync spell checker.
|
||||
*/
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.SPELLCHECK_SET_LANGUAGES,
|
||||
async (_, language: string): Promise<IPCResult<{ success: boolean }>> => {
|
||||
try {
|
||||
// Validate language parameter
|
||||
if (!language || typeof language !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid language parameter'
|
||||
};
|
||||
}
|
||||
|
||||
// Update tracked app language for context menu labels
|
||||
setAppLanguage(language);
|
||||
|
||||
// Get spell check languages for this app language
|
||||
const spellCheckLanguages = SPELL_CHECK_LANGUAGE_MAP[language] || [DEFAULT_SPELL_CHECK_LANGUAGE];
|
||||
|
||||
// Get available languages on this system
|
||||
const availableLanguages = session.defaultSession.availableSpellCheckerLanguages;
|
||||
|
||||
// Filter to only available languages
|
||||
const validLanguages = spellCheckLanguages.filter(lang =>
|
||||
availableLanguages.includes(lang)
|
||||
);
|
||||
|
||||
// Fallback to default if none of the preferred languages are available
|
||||
const languagesToSet = validLanguages.length > 0
|
||||
? validLanguages
|
||||
: (availableLanguages.includes(DEFAULT_SPELL_CHECK_LANGUAGE) ? [DEFAULT_SPELL_CHECK_LANGUAGE] : []);
|
||||
|
||||
if (languagesToSet.length > 0) {
|
||||
session.defaultSession.setSpellCheckerLanguages(languagesToSet);
|
||||
console.log(`[SPELLCHECK] Languages set to: ${languagesToSet.join(', ')} for app language: ${language}`);
|
||||
} else {
|
||||
console.warn(`[SPELLCHECK] No valid spell check languages available for: ${language}`);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: { success: true }
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[SPELLCHECK_SET_LANGUAGES] Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to set spell check languages'
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ export interface SettingsAPI {
|
||||
notifySentryStateChanged: (enabled: boolean) => void;
|
||||
getSentryDsn: () => Promise<string>;
|
||||
getSentryConfig: () => Promise<{ dsn: string; tracesSampleRate: number; profilesSampleRate: number }>;
|
||||
|
||||
// Spell check
|
||||
setSpellCheckLanguages: (language: string) => Promise<IPCResult<{ success: boolean }>>;
|
||||
}
|
||||
|
||||
export const createSettingsAPI = (): SettingsAPI => ({
|
||||
@@ -83,5 +86,9 @@ export const createSettingsAPI = (): SettingsAPI => ({
|
||||
|
||||
// Get full Sentry config from main process (DSN + sample rates)
|
||||
getSentryConfig: (): Promise<{ dsn: string; tracesSampleRate: number; profilesSampleRate: number }> =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.GET_SENTRY_CONFIG)
|
||||
ipcRenderer.invoke(IPC_CHANNELS.GET_SENTRY_CONFIG),
|
||||
|
||||
// Spell check - sync spell checker language with app language
|
||||
setSpellCheckLanguages: (language: string): Promise<IPCResult<{ success: boolean }>> =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.SPELLCHECK_SET_LANGUAGES, language)
|
||||
});
|
||||
|
||||
@@ -313,7 +313,24 @@ export function App() {
|
||||
if (settings.language && settings.language !== i18n.language) {
|
||||
i18n.changeLanguage(settings.language);
|
||||
}
|
||||
}, [settings.language, i18n]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- Only run when settings.language changes, not on every i18n object change
|
||||
}, [settings.language, i18n.language]);
|
||||
|
||||
// Sync spell check language with i18n language
|
||||
useEffect(() => {
|
||||
const syncSpellCheck = async () => {
|
||||
try {
|
||||
const result = await window.electronAPI.setSpellCheckLanguages(i18n.language);
|
||||
if (!result.success) {
|
||||
console.warn('[App] Failed to set spell check language:', result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[App] Error syncing spell check language:', error);
|
||||
}
|
||||
};
|
||||
|
||||
syncSpellCheck();
|
||||
}, [i18n.language]);
|
||||
|
||||
// Listen for open-app-settings events (e.g., from project settings)
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
'flex h-10 w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-primary',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'transition-colors duration-200',
|
||||
'file:border-0 file:bg-transparent file:text-sm file:font-medium',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Input.displayName = 'Input';
|
||||
|
||||
export { Input };
|
||||
import * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, spellCheck, lang, ...props }, ref) => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
spellCheck={spellCheck ?? true}
|
||||
lang={lang ?? i18n.language}
|
||||
className={cn(
|
||||
'flex h-10 w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-primary',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'transition-colors duration-200',
|
||||
'file:border-0 file:bg-transparent file:text-sm file:font-medium',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Input.displayName = 'Input';
|
||||
|
||||
export { Input };
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
'flex min-h-[80px] w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-primary',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'transition-colors duration-200',
|
||||
'resize-none',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Textarea.displayName = 'Textarea';
|
||||
|
||||
export { Textarea };
|
||||
import * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, spellCheck, lang, ...props }, ref) => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<textarea
|
||||
spellCheck={spellCheck ?? true}
|
||||
lang={lang ?? i18n.language}
|
||||
className={cn(
|
||||
'flex min-h-[80px] w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-primary',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'transition-colors duration-200',
|
||||
'resize-none',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Textarea.displayName = 'Textarea';
|
||||
|
||||
export { Textarea };
|
||||
|
||||
@@ -20,6 +20,9 @@ export const settingsMock = {
|
||||
getSentryDsn: async () => '', // No DSN in browser mode
|
||||
getSentryConfig: async () => ({ dsn: '', tracesSampleRate: 0, profilesSampleRate: 0 }),
|
||||
|
||||
// Spell check (no-op in browser mode)
|
||||
setSpellCheckLanguages: async () => ({ success: true, data: { success: true } }),
|
||||
|
||||
getCliToolsInfo: async () => ({
|
||||
success: true,
|
||||
data: {
|
||||
|
||||
@@ -1,37 +1,40 @@
|
||||
/**
|
||||
* Central export point for all constants
|
||||
* Re-exports from domain-specific constant modules
|
||||
*/
|
||||
|
||||
// Phase event protocol constants (Python ↔ TypeScript)
|
||||
export * from './phase-protocol';
|
||||
|
||||
// IPC Channel constants
|
||||
export * from './ipc';
|
||||
|
||||
// Task-related constants
|
||||
export * from './task';
|
||||
|
||||
// Roadmap constants
|
||||
export * from './roadmap';
|
||||
|
||||
// Ideation constants
|
||||
export * from './ideation';
|
||||
|
||||
// Changelog constants
|
||||
export * from './changelog';
|
||||
|
||||
// Model and agent profile constants
|
||||
export * from './models';
|
||||
|
||||
// Theme constants
|
||||
export * from './themes';
|
||||
|
||||
// GitHub integration constants
|
||||
export * from './github';
|
||||
|
||||
// API profile presets
|
||||
export * from './api-profiles';
|
||||
|
||||
// Configuration and paths
|
||||
export * from './config';
|
||||
/**
|
||||
* Central export point for all constants
|
||||
* Re-exports from domain-specific constant modules
|
||||
*/
|
||||
|
||||
// Phase event protocol constants (Python ↔ TypeScript)
|
||||
export * from './phase-protocol';
|
||||
|
||||
// IPC Channel constants
|
||||
export * from './ipc';
|
||||
|
||||
// Task-related constants
|
||||
export * from './task';
|
||||
|
||||
// Roadmap constants
|
||||
export * from './roadmap';
|
||||
|
||||
// Ideation constants
|
||||
export * from './ideation';
|
||||
|
||||
// Changelog constants
|
||||
export * from './changelog';
|
||||
|
||||
// Model and agent profile constants
|
||||
export * from './models';
|
||||
|
||||
// Theme constants
|
||||
export * from './themes';
|
||||
|
||||
// GitHub integration constants
|
||||
export * from './github';
|
||||
|
||||
// API profile presets
|
||||
export * from './api-profiles';
|
||||
|
||||
// Configuration and paths
|
||||
export * from './config';
|
||||
|
||||
// Spell check configuration
|
||||
export * from './spellcheck';
|
||||
|
||||
@@ -554,6 +554,9 @@ export const IPC_CHANNELS = {
|
||||
GET_SENTRY_DSN: 'sentry:get-dsn', // Get DSN from main process (env var)
|
||||
GET_SENTRY_CONFIG: 'sentry:get-config', // Get full Sentry config (DSN + sample rates)
|
||||
|
||||
// Spell check
|
||||
SPELLCHECK_SET_LANGUAGES: 'spellcheck:setLanguages', // Set spell check language (syncs with i18n)
|
||||
|
||||
// Screenshot capture
|
||||
SCREENSHOT_GET_SOURCES: 'screenshot:getSources', // Get available screens/windows
|
||||
SCREENSHOT_CAPTURE: 'screenshot:capture', // Capture screenshot from source
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Spell check language configuration constants.
|
||||
*
|
||||
* Maps app language codes to Chromium spell checker language codes.
|
||||
* Electron uses Chromium's spell checker which may use different codes
|
||||
* than standard locale codes (e.g., 'en-US' vs 'en').
|
||||
*/
|
||||
|
||||
/**
|
||||
* Map app language codes to spell checker language codes.
|
||||
* Each app language can map to multiple spell checker languages for better coverage.
|
||||
*/
|
||||
export const SPELL_CHECK_LANGUAGE_MAP: Record<string, string[]> = {
|
||||
en: ['en-US', 'en-GB'],
|
||||
fr: ['fr-FR', 'fr'],
|
||||
};
|
||||
|
||||
/**
|
||||
* Default spell check language when the preferred language isn't available.
|
||||
*/
|
||||
export const DEFAULT_SPELL_CHECK_LANGUAGE = 'en-US';
|
||||
|
||||
/**
|
||||
* Localized labels for "Add to Dictionary" context menu item.
|
||||
* Uses app language (not OS locale) to match the in-app language setting.
|
||||
*/
|
||||
export const ADD_TO_DICTIONARY_LABELS: Record<string, string> = {
|
||||
en: 'Add to Dictionary',
|
||||
fr: 'Ajouter au dictionnaire',
|
||||
};
|
||||
@@ -342,6 +342,9 @@ export interface ElectronAPI {
|
||||
getSettings: () => Promise<IPCResult<AppSettings>>;
|
||||
saveSettings: (settings: Partial<AppSettings>) => Promise<IPCResult>;
|
||||
|
||||
// Spell check
|
||||
setSpellCheckLanguages: (language: string) => Promise<IPCResult<{ success: boolean }>>;
|
||||
|
||||
// Sentry error reporting
|
||||
notifySentryStateChanged: (enabled: boolean) => void;
|
||||
getSentryDsn: () => Promise<string>;
|
||||
|
||||
Reference in New Issue
Block a user