Skip to content

Commit bca121f

Browse files
feat(desktop): localize workspace chrome
Signed-off-by: Sun-sunshine06 <Sun-sunshine06@users.noreply.github.com>
1 parent ed6e073 commit bca121f

13 files changed

Lines changed: 335 additions & 144 deletions

File tree

‎apps/desktop/src/main/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ function createWindow(): void {
3333
height: 820,
3434
minWidth: 960,
3535
minHeight: 640,
36+
autoHideMenuBar: process.platform !== 'darwin',
3637
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
3738
backgroundColor: BRAND.backgroundColor,
3839
show: false,

‎apps/desktop/src/renderer/src/components/CanvasErrorBar.tsx‎

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
/**
2-
* CanvasErrorBar — slim red strip shown above the preview iframe when the
3-
* sandbox runtime postMessages an IFRAME_ERROR.
4-
*
5-
* Loud surface (PRINCIPLES §10): every JS exception thrown inside the
6-
* generated HTML lands here with file + line. Users can dismiss the bar
7-
* (it clears the store slice) but errors are never auto-hidden.
8-
*/
9-
1+
import { useT } from '@open-codesign/i18n';
102
import { X } from 'lucide-react';
113
import { useCodesignStore } from '../store';
124

135
export function CanvasErrorBar() {
6+
const t = useT();
147
const errors = useCodesignStore((s) => s.iframeErrors);
158
const clear = useCodesignStore((s) => s.clearIframeErrors);
169
if (errors.length === 0) return null;
@@ -24,7 +17,8 @@ export function CanvasErrorBar() {
2417
<span className="mt-0.5 inline-block w-2 h-2 rounded-full bg-[var(--color-error)] shrink-0" />
2518
<div className="flex-1 min-w-0">
2619
<div className="text-xs font-semibold uppercase tracking-wide text-[var(--color-error)]">
27-
Preview runtime error{errors.length > 1 ? ` (${errors.length})` : ''}
20+
{t('preview.runtimeError')}
21+
{errors.length > 1 ? ` (${errors.length})` : ''}
2822
</div>
2923
<div className="text-sm text-[var(--color-text-primary)] truncate" title={latest}>
3024
{latest}
@@ -33,7 +27,7 @@ export function CanvasErrorBar() {
3327
<button
3428
type="button"
3529
onClick={clear}
36-
aria-label="Dismiss preview errors"
30+
aria-label={t('preview.dismissErrors')}
3731
className="shrink-0 p-1 rounded-[var(--radius-md)] text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors"
3832
>
3933
<X className="w-4 h-4" />

‎apps/desktop/src/renderer/src/components/CommandPalette.tsx‎

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useT } from '@open-codesign/i18n';
12
import { Download, Moon, Plus, Settings as SettingsIcon } from 'lucide-react';
23
import { useEffect, useMemo, useState } from 'react';
34
import { useCodesignStore } from '../store';
@@ -11,6 +12,7 @@ interface PaletteAction {
1112
}
1213

1314
export function CommandPalette() {
15+
const t = useT();
1416
const open = useCodesignStore((s) => s.commandPaletteOpen);
1517
const close = useCodesignStore((s) => s.closeCommandPalette);
1618
const openSettings = useCodesignStore((s) => s.openSettings);
@@ -24,8 +26,8 @@ export function CommandPalette() {
2426
() => [
2527
{
2628
id: 'new-design',
27-
label: 'New Design',
28-
hint: 'Clear chat and start fresh',
29+
label: t('commands.items.newDesign'),
30+
hint: t('commands.hints.newDesign'),
2931
icon: Plus,
3032
run: () => {
3133
useCodesignStore.setState({
@@ -35,44 +37,44 @@ export function CommandPalette() {
3537
iframeErrors: [],
3638
selectedElement: null,
3739
});
38-
pushToast({ variant: 'info', title: 'Workspace cleared' });
40+
pushToast({ variant: 'info', title: t('commands.cleared') });
3941
},
4042
},
4143
{
4244
id: 'toggle-theme',
43-
label: 'Toggle Theme',
44-
hint: 'Switch between light and dark',
45+
label: t('commands.items.toggleTheme'),
46+
hint: t('commands.hints.toggleTheme'),
4547
icon: Moon,
4648
run: toggleTheme,
4749
},
4850
{
4951
id: 'open-settings',
50-
label: 'Open Settings',
51-
hint: 'Models, appearance, storage',
52+
label: t('commands.items.openSettings'),
53+
hint: t('commands.hints.openSettings'),
5254
icon: SettingsIcon,
5355
run: openSettings,
5456
},
5557
{
5658
id: 'export',
57-
label: 'Export',
58-
hint: 'PDF and PPTX coming soon',
59+
label: t('commands.items.export'),
60+
hint: t('commands.hints.export'),
5961
icon: Download,
6062
run: () =>
6163
pushToast({
6264
variant: 'info',
63-
title: 'Export not available yet',
64-
description: 'PDF and PPTX exporters land in v0.2. HTML export is in the toolbar.',
65+
title: t('commands.exportUseToolbarTitle'),
66+
description: t('commands.exportUseToolbarBody'),
6567
}),
6668
},
6769
],
68-
[openSettings, toggleTheme, pushToast],
70+
[openSettings, pushToast, t, toggleTheme],
6971
);
7072

7173
const filtered = useMemo(() => {
7274
const q = query.trim().toLowerCase();
7375
if (!q) return actions;
7476
return actions.filter(
75-
(a) => a.label.toLowerCase().includes(q) || a.hint.toLowerCase().includes(q),
77+
(action) => action.label.toLowerCase().includes(q) || action.hint.toLowerCase().includes(q),
7678
);
7779
}, [actions, query]);
7880

@@ -89,8 +91,8 @@ export function CommandPalette() {
8991

9092
if (!open) return null;
9193

92-
function runAt(i: number) {
93-
const action = filtered[i];
94+
function runAt(index: number) {
95+
const action = filtered[index];
9496
if (!action) return;
9597
action.run();
9698
close();
@@ -101,7 +103,7 @@ export function CommandPalette() {
101103
// biome-ignore lint/a11y/useSemanticElements: native <dialog> top-layer rendering interferes with our overlay stack
102104
role="dialog"
103105
aria-modal="true"
104-
aria-label="Command palette"
106+
aria-label={t('commands.title')}
105107
className="fixed inset-0 z-50 flex items-start justify-center pt-24 px-6 bg-[var(--color-overlay)] animate-[overlay-in_120ms_ease-out]"
106108
onClick={close}
107109
onKeyDown={(e) => {
@@ -114,10 +116,10 @@ export function CommandPalette() {
114116
onKeyDown={(e) => {
115117
if (e.key === 'ArrowDown') {
116118
e.preventDefault();
117-
setCursor((c) => Math.min(c + 1, filtered.length - 1));
119+
setCursor((current) => Math.min(current + 1, filtered.length - 1));
118120
} else if (e.key === 'ArrowUp') {
119121
e.preventDefault();
120-
setCursor((c) => Math.max(c - 1, 0));
122+
setCursor((current) => Math.max(current - 1, 0));
121123
} else if (e.key === 'Enter') {
122124
e.preventDefault();
123125
runAt(cursor);
@@ -136,24 +138,24 @@ export function CommandPalette() {
136138
setQuery(e.target.value);
137139
setCursor(0);
138140
}}
139-
placeholder="Type a command…"
141+
placeholder={t('commands.placeholder')}
140142
className="w-full px-5 h-12 bg-transparent border-b border-[var(--color-border)] text-[var(--text-sm)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none"
141143
/>
142144
<ul className="max-h-72 overflow-y-auto py-2">
143145
{filtered.length === 0 ? (
144146
<li className="px-5 py-3 text-[var(--text-sm)] text-[var(--color-text-muted)]">
145-
No matches.
147+
{t('commands.noMatches')}
146148
</li>
147149
) : (
148-
filtered.map((a, i) => {
149-
const Icon = a.icon;
150-
const active = i === cursor;
150+
filtered.map((action, index) => {
151+
const Icon = action.icon;
152+
const active = index === cursor;
151153
return (
152-
<li key={a.id}>
154+
<li key={action.id}>
153155
<button
154156
type="button"
155-
onMouseEnter={() => setCursor(i)}
156-
onClick={() => runAt(i)}
157+
onMouseEnter={() => setCursor(index)}
158+
onClick={() => runAt(index)}
157159
className={`w-full flex items-center gap-3 px-5 py-2.5 text-left transition-colors ${
158160
active
159161
? 'bg-[var(--color-surface-active)]'
@@ -163,10 +165,10 @@ export function CommandPalette() {
163165
<Icon className="w-4 h-4 text-[var(--color-text-secondary)] shrink-0" />
164166
<span className="flex-1 min-w-0">
165167
<span className="block text-[var(--text-sm)] text-[var(--color-text-primary)]">
166-
{a.label}
168+
{action.label}
167169
</span>
168170
<span className="block text-[var(--text-xs)] text-[var(--color-text-muted)]">
169-
{a.hint}
171+
{action.hint}
170172
</span>
171173
</span>
172174
</button>

‎apps/desktop/src/renderer/src/components/InlineCommentComposer.tsx‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useT } from '@open-codesign/i18n';
12
import type { SelectedElement } from '@open-codesign/shared';
23
import { MessageSquareText, X } from 'lucide-react';
34
import { useState } from 'react';
@@ -16,6 +17,7 @@ interface InlineCommentComposerCardProps {
1617
}
1718

1819
function InlineCommentComposerCard({ selectedElement }: InlineCommentComposerCardProps) {
20+
const t = useT();
1921
const clearCanvasElement = useCodesignStore((s) => s.clearCanvasElement);
2022
const applyInlineComment = useCodesignStore((s) => s.applyInlineComment);
2123
const isGenerating = useCodesignStore((s) => s.isGenerating);
@@ -27,7 +29,7 @@ function InlineCommentComposerCard({ selectedElement }: InlineCommentComposerCar
2729
<div className="min-w-0">
2830
<div className="inline-flex items-center gap-2 text-[12px] font-medium text-[var(--color-text-primary)]">
2931
<MessageSquareText className="h-4 w-4 text-[var(--color-accent)]" />
30-
Comment on <code className="text-[11px]">{selectedElement.tag}</code>
32+
{t('inlineComment.title')} <code className="text-[11px]">{selectedElement.tag}</code>
3133
</div>
3234
<p
3335
className="mt-1 truncate text-[11px] text-[var(--color-text-muted)]"
@@ -40,21 +42,20 @@ function InlineCommentComposerCard({ selectedElement }: InlineCommentComposerCar
4042
type="button"
4143
onClick={clearCanvasElement}
4244
className="rounded-[var(--radius-md)] p-1 text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)]"
43-
aria-label="Close inline comment composer"
45+
aria-label={t('inlineComment.closeComposer')}
4446
>
4547
<X className="h-4 w-4" />
4648
</button>
4749
</div>
4850

4951
<div className="space-y-3 p-4">
5052
<p className="text-[12px] leading-[1.5] text-[var(--color-text-secondary)]">
51-
Clicked elements stay selected in the canvas. Describe the visual or content change you
52-
want, and open-codesign will rewrite the artifact around that target.
53+
{t('inlineComment.description')}
5354
</p>
5455
<textarea
5556
value={draft}
5657
onChange={(e) => setDraft(e.target.value)}
57-
placeholder="Make this section more compact, sharpen the headline, and align it with the linked design system..."
58+
placeholder={t('inlineComment.placeholder')}
5859
rows={4}
5960
disabled={isGenerating}
6061
className="w-full resize-none rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-background)] px-3 py-2 text-[13px] leading-[1.5] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] transition-[box-shadow,border-color] duration-150 focus:border-[var(--color-accent)] focus:shadow-[0_0_0_3px_var(--color-focus-ring)] focus:outline-none"
@@ -65,15 +66,15 @@ function InlineCommentComposerCard({ selectedElement }: InlineCommentComposerCar
6566
onClick={clearCanvasElement}
6667
className="text-[12px] text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]"
6768
>
68-
Cancel
69+
{t('common.cancel')}
6970
</button>
7071
<button
7172
type="button"
7273
disabled={!draft.trim() || isGenerating}
7374
onClick={() => void applyInlineComment(draft)}
7475
className="inline-flex items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-accent)] px-3 py-2 text-[12px] font-medium text-white shadow-[var(--shadow-soft)] transition-colors hover:bg-[var(--color-accent-hover)] disabled:pointer-events-none disabled:opacity-40"
7576
>
76-
{isGenerating ? 'Applying...' : 'Apply change'}
77+
{isGenerating ? t('inlineComment.applying') : t('inlineComment.applyChange')}
7778
</button>
7879
</div>
7980
</div>

‎apps/desktop/src/renderer/src/components/PreviewPane.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useT } from '@open-codesign/i18n';
12
import { buildSrcdoc, isIframeErrorMessage, isOverlayMessage } from '@open-codesign/runtime';
23
import { useEffect, useRef } from 'react';
34
import { EmptyState } from '../preview/EmptyState';
@@ -20,6 +21,7 @@ export function isTrustedPreviewMessageSource(
2021
}
2122

2223
export function PreviewPane({ onPickStarter }: PreviewPaneProps) {
24+
const t = useT();
2325
const previewHtml = useCodesignStore((s) => s.previewHtml);
2426
const isGenerating = useCodesignStore((s) => s.isGenerating);
2527
const errorMessage = useCodesignStore((s) => s.errorMessage);
@@ -74,7 +76,7 @@ export function PreviewPane({ onPickStarter }: PreviewPaneProps) {
7476
<div className="h-full p-6">
7577
<div className="relative h-full">
7678
<div className="absolute left-5 top-5 z-10 rounded-full border border-[var(--color-border)] bg-[rgba(255,255,255,0.88)] px-3 py-1 text-[11px] text-[var(--color-text-secondary)] shadow-[var(--shadow-soft)] backdrop-blur">
77-
Click any element in the preview to leave an inline comment.
79+
{t('preview.clickToComment')}
7880
</div>
7981
<iframe
8082
ref={iframeRef}

‎apps/desktop/src/renderer/src/components/PreviewToolbar.tsx‎

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useT } from '@open-codesign/i18n';
12
import { Download } from 'lucide-react';
23
import { type ReactElement, useEffect, useRef, useState } from 'react';
34
import type { ExportFormat } from '../../../preload/index';
@@ -10,14 +11,8 @@ interface ExportItem {
1011
ready: boolean;
1112
}
1213

13-
const EXPORT_ITEMS: ExportItem[] = [
14-
{ format: 'html', label: 'HTML', ready: true, hint: 'Single self-contained .html file' },
15-
{ format: 'pdf', label: 'PDF', ready: true, hint: 'Rendered via your installed Chrome' },
16-
{ format: 'pptx', label: 'PPTX', ready: true, hint: 'Editable slides; one per <section>' },
17-
{ format: 'zip', label: 'ZIP bundle', ready: true, hint: 'index.html + assets + README.md' },
18-
];
19-
2014
export function PreviewToolbar(): ReactElement {
15+
const t = useT();
2116
const previewHtml = useCodesignStore((s) => s.previewHtml);
2217
const exportActive = useCodesignStore((s) => s.exportActive);
2318
const toastMessage = useCodesignStore((s) => s.toastMessage);
@@ -36,11 +31,37 @@ export function PreviewToolbar(): ReactElement {
3631

3732
useEffect(() => {
3833
if (!toastMessage) return;
39-
const t = setTimeout(() => dismissToast(), 4000);
40-
return () => clearTimeout(t);
34+
const timeout = setTimeout(() => dismissToast(), 4000);
35+
return () => clearTimeout(timeout);
4136
}, [toastMessage, dismissToast]);
4237

4338
const disabled = !previewHtml;
39+
const exportItems: ExportItem[] = [
40+
{
41+
format: 'html',
42+
label: t('export.items.html.label'),
43+
ready: true,
44+
hint: t('export.items.html.hint'),
45+
},
46+
{
47+
format: 'pdf',
48+
label: t('export.items.pdf.label'),
49+
ready: true,
50+
hint: t('export.items.pdf.hint'),
51+
},
52+
{
53+
format: 'pptx',
54+
label: t('export.items.pptx.label'),
55+
ready: true,
56+
hint: t('export.items.pptx.hint'),
57+
},
58+
{
59+
format: 'zip',
60+
label: t('export.items.zip.label'),
61+
ready: true,
62+
hint: t('export.items.zip.hint'),
63+
},
64+
];
4465

4566
return (
4667
<div className="flex items-center justify-end gap-2 px-6 py-2 border-b border-[var(--color-border-muted)] bg-[var(--color-background-secondary)]">
@@ -60,15 +81,15 @@ export function PreviewToolbar(): ReactElement {
6081
aria-expanded={open}
6182
>
6283
<Download className="w-[14px] h-[14px]" aria-hidden="true" />
63-
Export
84+
{t('export.button')}
6485
</button>
6586

6687
{open && (
6788
<div
6889
role="menu"
6990
className="absolute right-0 top-full mt-2 min-w-[200px] rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-surface)] shadow-[var(--shadow-elevated)] py-1 z-10"
7091
>
71-
{EXPORT_ITEMS.map((item) => (
92+
{exportItems.map((item) => (
7293
<button
7394
key={item.format}
7495
type="button"

0 commit comments

Comments
 (0)