Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions openless-all/app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions openless-all/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"check:hotkey-injection": "node scripts/check-hotkey-injection.mjs"
},
"dependencies": {
"@fontsource/inter": "^5.2.8",
"@fontsource/noto-sans-jp": "^5.2.9",
"@tauri-apps/api": "^2.1.1",
"@tauri-apps/plugin-autostart": "^2.5.1",
"@tauri-apps/plugin-dialog": "^2.7.1",
Expand Down
149 changes: 149 additions & 0 deletions openless-all/app/src/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import { Icon } from './Icon';
import { AboutUpdateControl, Settings as SettingsContent, Toggle, type SettingsSectionId } from '../pages/Settings';
import { Row } from './ui/Row';
import { readFontScale, setFontScale, type FontScaleId } from '../lib/fontScale';
import {
type FontFamilyId,
readFontFamily,
readFontFamilyCustom,
setFontFamily,
querySystemFonts,
} from '../lib/fontFamily';
import { readQuietCompletion, setQuietCompletion } from '../lib/quietMode';
import {
exportErrorLog,
fetchLatestBetaRelease,
Expand Down Expand Up @@ -222,6 +230,17 @@ function PersonalizeSection() {
['large', t('modal.personalize.fontLarge')],
];

// UI フォント(PolishMode の出力スタイルではなく、アプリ内 UI のフォントファミリ)。
// localStorage キーは fontFamily.ts 側で管理(`ol-font-family` / `ol-font-family-custom`)。
const [fontFamilyId, setFontFamilyIdState] = useState<FontFamilyId>(() => readFontFamily());
const [fontFamilyCustom, setFontFamilyCustomState] = useState<string>(() => readFontFamilyCustom());
const [systemFonts, setSystemFonts] = useState<string[]>([]);
const [systemFontsLoading, setSystemFontsLoading] = useState(false);
const [systemFontsError, setSystemFontsError] = useState<string | null>(null);

// サイレントモード — Capsule のテキストオーバーレイ抑制。`ol-quiet-completion` をそのまま使う。
const [quietMode, setQuietModeState] = useState<boolean>(() => readQuietCompletion());

return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<Row label={t('modal.personalize.language')}>
Expand Down Expand Up @@ -257,6 +276,136 @@ function PersonalizeSection() {
})}
</div>
</Row>
<Row label={t('modal.personalize.fontFamilyLabel')} desc={t('modal.personalize.fontFamilyDesc')}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, width: '100%' }}>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
<select
value={fontFamilyId}
onChange={e => {
const v = e.target.value;
if (v.startsWith('sys:')) {
const name = v.slice(4);
setFontFamilyIdState('custom');
setFontFamilyCustomState(name);
setFontFamily('custom', name);
return;
}
const id = v as FontFamilyId;
setFontFamilyIdState(id);
setFontFamily(id, fontFamilyCustom);
}}
style={{
height: 32, padding: '0 10px',
border: '0.5px solid var(--ol-line-strong)',
borderRadius: 8, fontSize: 12.5,
fontFamily: 'inherit', outline: 'none',
background: 'var(--ol-surface-2)',
minWidth: 220, cursor: 'default',
}}
>
<option value="auto">{t('modal.personalize.fontFamilyAuto')}</option>
<option value="notoSansJp">Noto Sans JP(源ノ角ゴシック JP)</option>
<option value="yuGothic">游ゴシック</option>
<option value="meiryo">メイリオ</option>
<option value="hiragino">ヒラギノ角ゴ(macOS)</option>
<option value="custom">{t('modal.personalize.fontFamilyCustomLabel')}</option>
{systemFonts.length > 0 && (
<optgroup label={t('modal.personalize.fontInstalledLabel', { count: systemFonts.length })}>
{systemFonts.map(name => (
<option key={name} value={`sys:${name}`}>
{name}
</option>
))}
</optgroup>
)}
</select>
<button
onClick={async () => {
setSystemFontsLoading(true);
setSystemFontsError(null);
const fonts = await querySystemFonts();
setSystemFontsLoading(false);
if (fonts.length === 0) {
setSystemFontsError(t('modal.personalize.fontLoadError'));
} else {
setSystemFonts(fonts);
}
}}
disabled={systemFontsLoading}
style={btnGhost}
>
{systemFontsLoading
? t('modal.personalize.fontLoading')
: systemFonts.length > 0
? t('modal.personalize.fontReload', { count: systemFonts.length })
: t('modal.personalize.fontLoadAll')}
</button>
</div>
{fontFamilyId === 'custom' && (
<input
type="text"
value={fontFamilyCustom}
onChange={e => {
const name = e.target.value;
setFontFamilyCustomState(name);
setFontFamily('custom', name);
}}
placeholder={t('modal.personalize.fontFamilyCustomPlaceholder')}
style={{
padding: '6px 10px',
borderRadius: 8,
border: '0.5px solid var(--ol-line-strong)',
background: 'var(--ol-surface)',
color: 'var(--ol-ink)',
fontFamily: 'inherit',
fontSize: 12.5,
width: '100%',
maxWidth: 360,
}}
/>
)}
{systemFontsError && (
<div role="alert" style={{ fontSize: 11, color: 'var(--ol-err)', lineHeight: 1.5 }}>
{systemFontsError}
</div>
)}
</div>
</Row>
<Row label={t('modal.personalize.quietLabel')} desc={t('modal.personalize.quietDesc')}>
<button
onClick={() => {
const next = !quietMode;
setQuietModeState(next);
setQuietCompletion(next);
}}
aria-label={t('modal.personalize.quietAria')}
style={{
position: 'relative',
flexShrink: 0,
width: 36,
height: 20,
borderRadius: 999,
border: 0,
background: quietMode ? 'var(--ol-blue)' : 'rgba(0,0,0,0.15)',
cursor: 'default',
transition: 'background 0.16s var(--ol-motion-quick)',
}}
>
<span
style={{
position: 'absolute',
top: 2,
left: quietMode ? 18 : 2,
width: 16,
height: 16,
borderRadius: 999,
background: '#fff',
boxShadow: '0 1px 2px rgba(0,0,0,.2)',
transition: 'left .16s var(--ol-motion-spring)',
}}
/>
</button>
</Row>
<Row label={t('modal.personalize.blur')} desc={t('modal.personalize.blurDesc')}>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
<input
Expand Down
52 changes: 52 additions & 0 deletions openless-all/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,52 @@ export const en: typeof zhCN = {
structured: { name: 'Structured', desc: 'Auto-organizes into a numbered outline when you cover several topics or steps.', sample: '1. Topic one\na. Point\nb. Point\n2. Topic two\na. Point\nb. Point' },
formal: { name: 'Formal', desc: 'Email and workplace tone — more complete, more professional.', sample: 'Detects greetings/sign-offs in email contexts; avoids empty pleasantries.' },
},
customMode: {
title: 'Custom styles',
desc: 'Add your own polish styles with custom system prompts in addition to the four builtin ones.',
addButton: '+ Add new custom style',
empty: 'No custom styles yet.',
idLabel: 'ID (immutable)',
idPlaceholder: 'e.g. my-style',
idHint: 'Alphanumeric and dashes only. Colon (:) is not allowed.',
nameLabel: 'Display name',
namePlaceholder: 'e.g. Casual notes',
promptLabel: 'System prompt',
promptPlaceholder: 'Write the instructions you want to pass to the LLM…',
delete: 'Delete',
edit: 'Edit',
save: 'Save',
cancel: 'Cancel',
viewBuiltinPrompt: 'View default prompt',
hideBuiltinPrompt: 'Hide',
confirmDelete: 'Delete this custom style?',
idEmpty: 'Please enter an ID.',
idInvalid: 'ID must be alphanumeric or dashes only.',
nameEmpty: 'Please enter a display name.',
promptEmpty: 'Please enter a system prompt.',
},
appOverride: {
title: 'Per-app auto-switch',
desc: 'Automatically apply a specific style when dictating in a given app. The pattern matches the process name as a substring (e.g. chrome, Discord.exe). When nothing matches, the default style is used.',
addButton: '+ Add new rule',
empty: 'No rules yet. Click "+ Add new rule" to start.',
patternLabel: 'App name (substring)',
patternPlaceholder: 'e.g. chrome / Discord.exe',
modeLabel: 'Style to apply',
delete: 'Delete',
hint: 'Rules are evaluated top-to-bottom; the first match wins. Matching is case-insensitive.',
},
},
translation: {
kicker: 'TRANSLATION',
title: 'Translation',
desc: 'Translate the dictation into a target language before insertion. Target, working languages and trigger are configured here.',
statusEnabled: 'Enabled',
statusDisabled: 'Disabled',
enable: {
title: 'Enable translation',
hint: 'When off, an accidental hotkey press will not start the translation pipeline or the UI overlay.',
},
working: {
title: 'Working languages',
desc: 'Pick the languages you regularly use (multi-select). These are passed to the LLM as a premise so polish and translation know which spellings, tone, and conventions you expect.',
Expand Down Expand Up @@ -543,6 +582,19 @@ export const en: typeof zhCN = {
fontSmall: 'Small',
fontMedium: 'Medium',
fontLarge: 'Large',
fontFamilyLabel: 'UI font',
fontFamilyDesc: 'Switch the in-app display font. Useful when Japanese text is rendered in a Chinese font, or when you prefer another font.',
fontFamilyAuto: 'Auto (default priority)',
fontFamilyCustomLabel: 'Custom (free input)…',
fontFamilyCustomPlaceholder: 'Enter font name (e.g. Yu Gothic UI)',
fontInstalledLabel: 'Installed fonts ({{count}})',
fontLoadAll: 'Load all fonts',
fontReload: 'Reload ({{count}})',
fontLoading: 'Loading…',
fontLoadError: 'Failed to load font list (permission denied or unsupported environment)',
quietLabel: 'Quiet mode',
quietDesc: 'Keeps the recording waveform and processing-dot animation, but suppresses transient text like "Translating", "N chars typed", or "Cancelled". Errors are still shown.',
quietAria: 'Toggle quiet mode',
blur: 'Glass blur intensity',
blurDesc: 'Affects the inner backdrop-filter strength (the macOS system frosted layer can not be tuned at runtime).',
startupOpen: 'On launch',
Expand Down
Loading