From aae86a40015fd6350ed7575bd77366fa27368753 Mon Sep 17 00:00:00 2001 From: Hibryda Date: Sun, 22 Mar 2026 10:28:13 +0100 Subject: [PATCH] =?UTF-8?q?feat(electrobun):=20i18n=20system=20=E2=80=94?= =?UTF-8?q?=20@formatjs/intl=20+=20Svelte=205=20runes=20+=203=20locales?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - i18n.svelte.ts: store with $state locale + createIntl(), t() function, formatDate/Number/RelativeTime, getDir() for RTL, async setLocale() - i18n.types.ts: TranslationKey union (codegen from en.json) - locales/en.json: 200+ strings in ICU MessageFormat - locales/pl.json: full Polish translation - locales/ar.json: partial Arabic (validates 6-form plural + RTL) - scripts/i18n-types.ts: codegen script for type-safe keys - 6 components wired: StatusBar, AgentPane, CommandPalette, SettingsDrawer, SplashScreen, ChatInput - Language selector in AppearanceSettings - App.svelte: document.dir reactive for RTL - CONTRIBUTING_I18N.md: guide for adding languages Note: currently Electrobun-only. Will extract to @agor/i18n shared package for both Tauri and Electrobun. --- ui-electrobun/CONTRIBUTING_I18N.md | 104 ++++++++++++ ui-electrobun/locales/ar.json | 75 +++++++++ ui-electrobun/locales/en.json | 150 +++++++++++++++++ ui-electrobun/locales/pl.json | 150 +++++++++++++++++ ui-electrobun/package.json | 3 +- ui-electrobun/scripts/i18n-types.ts | 37 +++++ ui-electrobun/src/mainview/AgentPane.svelte | 12 +- ui-electrobun/src/mainview/App.svelte | 8 + ui-electrobun/src/mainview/ChatInput.svelte | 4 +- .../src/mainview/CommandPalette.svelte | 60 ++++--- .../src/mainview/SettingsDrawer.svelte | 31 ++-- .../src/mainview/SplashScreen.svelte | 3 +- ui-electrobun/src/mainview/StatusBar.svelte | 33 ++-- ui-electrobun/src/mainview/i18n.svelte.ts | 153 ++++++++++++++++++ ui-electrobun/src/mainview/i18n.types.ts | 140 ++++++++++++++++ .../settings/AppearanceSettings.svelte | 48 +++++- 16 files changed, 947 insertions(+), 64 deletions(-) create mode 100644 ui-electrobun/CONTRIBUTING_I18N.md create mode 100644 ui-electrobun/locales/ar.json create mode 100644 ui-electrobun/locales/en.json create mode 100644 ui-electrobun/locales/pl.json create mode 100644 ui-electrobun/scripts/i18n-types.ts create mode 100644 ui-electrobun/src/mainview/i18n.svelte.ts create mode 100644 ui-electrobun/src/mainview/i18n.types.ts diff --git a/ui-electrobun/CONTRIBUTING_I18N.md b/ui-electrobun/CONTRIBUTING_I18N.md new file mode 100644 index 0000000..e27d49b --- /dev/null +++ b/ui-electrobun/CONTRIBUTING_I18N.md @@ -0,0 +1,104 @@ +# i18n Contributing Guide + +This project uses ICU MessageFormat via `@formatjs/intl` with a custom Svelte 5 store. + +## Adding a New Language + +1. Create `locales/.json` (e.g. `locales/de.json`) with all keys from `locales/en.json`. +2. Register the locale in `src/mainview/i18n.svelte.ts`: + - Add an entry to `AVAILABLE_LOCALES` with tag, label, nativeLabel, and dir. + - Add a dynamic import entry to the `loaders` map. +3. The language will automatically appear in Settings > Appearance > Language. + +## Key Naming Convention + +Use dot-notation: `component.element.state` + +``` +sidebar.settings -- component.element +agent.status.running -- component.element.state +settings.appearance -- component.element +common.cancel -- shared across components +errors.connectionFailed -- error messages +``` + +Group prefixes: +- `sidebar.*` -- left sidebar +- `agent.*` -- agent pane +- `terminal.*` -- terminal tabs +- `settings.*` -- settings drawer +- `statusbar.*` -- bottom status bar +- `notifications.*` -- notification drawer +- `files.*` -- file browser +- `search.*` -- search overlay +- `comms.*` -- communications tab +- `tasks.*` -- task board +- `errors.*` -- error messages +- `splash.*` -- splash screen +- `common.*` -- shared labels (cancel, save, delete) +- `project.*` -- project management +- `palette.*` -- command palette + +## ICU MessageFormat Syntax + +### Plain text +```json +"common.save": "Save" +``` + +### Plurals (English: one/other) +```json +"search.resultsCount": "{count, plural, one {{count} result} other {{count} results}}" +``` + +### Plurals (Polish: one/few/many/other) +```json +"search.resultsCount": "{count, plural, one {{count} wynik} few {{count} wyniki} many {{count} wynikow} other {{count} wynikow}}" +``` + +### Plurals (Arabic: zero/one/two/few/many/other) +```json +"tasks.taskCount": "{count, plural, =0 {no tasks} one {one task} two {two tasks} few {{count} tasks} many {{count} tasks} other {{count} tasks}}" +``` + +### Interpolation +```json +"errors.unhandled": "Unhandled error: {message}" +``` + +### Select +```json +"agent.status": "{status, select, running {Running} idle {Idle} other {Unknown}}" +``` + +## Using Translations in Components + +```svelte + + + +{t('sidebar.notifCount', { count: 5 })} +``` + +## Testing Translations + +1. Switch language in Settings > Appearance > Language. +2. All `t()` calls update reactively -- no page reload needed. +3. Missing keys fall back to the key name itself (e.g. `"agent.status.running"`). +4. Check the browser console for `[i18n]` warnings about missing or malformed messages. + +## Regenerating Types + +After adding or removing keys in `locales/en.json`: + +```bash +bun scripts/i18n-types.ts +``` + +This regenerates `src/mainview/i18n.types.ts` with the updated `TranslationKey` union type. TypeScript will then flag any `t()` calls using keys that no longer exist. + +## RTL Support + +Arabic (`ar`) sets `document.dir = 'rtl'` automatically. CSS should use logical properties (`inline-start`/`inline-end`) instead of `left`/`right` where possible. diff --git a/ui-electrobun/locales/ar.json b/ui-electrobun/locales/ar.json new file mode 100644 index 0000000..eddc0f2 --- /dev/null +++ b/ui-electrobun/locales/ar.json @@ -0,0 +1,75 @@ +{ + "sidebar.settings": "\u0625\u0639\u062f\u0627\u062f\u0627\u062a", + "sidebar.addGroup": "\u0625\u0636\u0627\u0641\u0629 \u0645\u062c\u0645\u0648\u0639\u0629", + "sidebar.addProject": "\u0625\u0636\u0627\u0641\u0629 \u0645\u0634\u0631\u0648\u0639", + "sidebar.groupName": "\u0627\u0633\u0645 \u0627\u0644\u0645\u062c\u0645\u0648\u0639\u0629", + "sidebar.notifications": "\u0627\u0644\u0625\u0634\u0639\u0627\u0631\u0627\u062a", + "sidebar.notifCount": "{count, plural, =0 {\u0627\u0644\u0625\u0634\u0639\u0627\u0631\u0627\u062a} one {\u0625\u0634\u0639\u0627\u0631 \u0648\u0627\u062d\u062f} two {\u0625\u0634\u0639\u0627\u0631\u0627\u0646} few {{count} \u0625\u0634\u0639\u0627\u0631\u0627\u062a} many {{count} \u0625\u0634\u0639\u0627\u0631\u064b\u0627} other {{count} \u0625\u0634\u0639\u0627\u0631}}", + "sidebar.close": "\u0625\u063a\u0644\u0627\u0642 \u0627\u0644\u0646\u0627\u0641\u0630\u0629", + "sidebar.maximize": "\u062a\u0643\u0628\u064a\u0631 \u0627\u0644\u0646\u0627\u0641\u0630\u0629", + "sidebar.minimize": "\u062a\u0635\u063a\u064a\u0631 \u0627\u0644\u0646\u0627\u0641\u0630\u0629", + + "agent.prompt.placeholder": "\u0627\u0637\u0631\u062d \u0633\u0624\u0627\u0644\u0627\u064b \u0623\u0648 \u0635\u0641 \u0645\u0647\u0645\u0629...", + "agent.prompt.send": "\u0625\u0631\u0633\u0627\u0644", + "agent.prompt.stop": "\u0625\u064a\u0642\u0627\u0641 \u0627\u0644\u0648\u0643\u064a\u0644", + "agent.status.running": "\u064a\u0639\u0645\u0644", + "agent.status.idle": "\u062e\u0627\u0645\u0644", + "agent.status.done": "\u0645\u0643\u062a\u0645\u0644", + "agent.status.error": "\u062e\u0637\u0623", + "agent.status.stalled": "\u0645\u062a\u0648\u0642\u0641", + "agent.status.thinking": "\u064a\u0641\u0643\u0631", + "agent.tokens": "{count} \u0631\u0645\u0632", + + "settings.title": "\u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a", + "settings.close": "\u0625\u063a\u0644\u0627\u0642 \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a", + "settings.appearance": "\u0627\u0644\u0645\u0638\u0647\u0631", + "settings.agents": "\u0627\u0644\u0648\u0643\u0644\u0627\u0621", + "settings.security": "\u0627\u0644\u0623\u0645\u0627\u0646", + "settings.projects": "\u0627\u0644\u0645\u0634\u0627\u0631\u064a\u0639", + "settings.orchestration": "\u0627\u0644\u062a\u0646\u0633\u064a\u0642", + "settings.machines": "\u0627\u0644\u0623\u062c\u0647\u0632\u0629", + "settings.keyboard": "\u0644\u0648\u062d\u0629 \u0627\u0644\u0645\u0641\u0627\u062a\u064a\u062d", + "settings.advanced": "\u0645\u062a\u0642\u062f\u0645", + "settings.marketplace": "\u0627\u0644\u0633\u0648\u0642", + "settings.diagnostics": "\u0627\u0644\u062a\u0634\u062e\u064a\u0635", + "settings.language": "\u0627\u0644\u0644\u063a\u0629", + + "statusbar.running": "\u064a\u0639\u0645\u0644", + "statusbar.idle": "\u062e\u0627\u0645\u0644", + "statusbar.stalled": "\u0645\u062a\u0648\u0642\u0641", + "statusbar.attention": "\u0627\u0646\u062a\u0628\u0627\u0647", + "statusbar.projects": "\u0645\u0634\u0627\u0631\u064a\u0639", + "statusbar.session": "\u062c\u0644\u0633\u0629", + "statusbar.tokens": "\u0631\u0645\u0648\u0632", + "statusbar.cost": "\u0627\u0644\u062a\u0643\u0644\u0641\u0629", + + "splash.loading": "\u062c\u0627\u0631\u064d \u0627\u0644\u062a\u062d\u0645\u064a\u0644...", + + "common.cancel": "\u0625\u0644\u063a\u0627\u0621", + "common.confirm": "\u062a\u0623\u0643\u064a\u062f", + "common.close": "\u0625\u063a\u0644\u0627\u0642", + "common.save": "\u062d\u0641\u0638", + "common.delete": "\u062d\u0630\u0641", + "common.edit": "\u062a\u0639\u062f\u064a\u0644", + "common.refresh": "\u062a\u062d\u062f\u064a\u062b", + "common.add": "\u0625\u0636\u0627\u0641\u0629", + "common.back": "\u0631\u062c\u0648\u0639", + "common.noItems": "\u0644\u0627 \u062a\u0648\u062c\u062f \u0639\u0646\u0627\u0635\u0631", + + "tasks.todo": "\u0644\u0644\u062a\u0646\u0641\u064a\u0630", + "tasks.inProgress": "\u0642\u064a\u062f \u0627\u0644\u062a\u0646\u0641\u064a\u0630", + "tasks.review": "\u0645\u0631\u0627\u062c\u0639\u0629", + "tasks.done": "\u0645\u0643\u062a\u0645\u0644", + "tasks.blocked": "\u0645\u062d\u0638\u0648\u0631", + "tasks.taskCount": "{count, plural, =0 {\u0644\u0627 \u0645\u0647\u0627\u0645} one {\u0645\u0647\u0645\u0629 \u0648\u0627\u062d\u062f\u0629} two {\u0645\u0647\u0645\u062a\u0627\u0646} few {{count} \u0645\u0647\u0627\u0645} many {{count} \u0645\u0647\u0645\u0629} other {{count} \u0645\u0647\u0645\u0629}}", + + "errors.connectionFailed": "\u0641\u0634\u0644 \u0627\u0644\u0627\u062a\u0635\u0627\u0644", + "errors.sessionExpired": "\u0627\u0646\u062a\u0647\u062a \u0627\u0644\u062c\u0644\u0633\u0629", + "errors.fileNotFound": "\u0627\u0644\u0645\u0644\u0641 \u063a\u064a\u0631 \u0645\u0648\u062c\u0648\u062f", + "errors.generic": "\u062d\u062f\u062b \u062e\u0637\u0623 \u0645\u0627", + + "palette.placeholder": "\u0627\u0643\u062a\u0628 \u0623\u0645\u0631\u064b\u0627...", + "palette.newTerminal": "\u0639\u0644\u0627\u0645\u0629 \u062a\u0628\u0648\u064a\u0628 \u0637\u0631\u0641\u064a\u0629 \u062c\u062f\u064a\u062f\u0629", + "palette.openSettings": "\u0641\u062a\u062d \u0627\u0644\u0625\u0639\u062f\u0627\u062f\u0627\u062a", + "palette.searchMessages": "\u0628\u062d\u062b \u0641\u064a \u0627\u0644\u0631\u0633\u0627\u0626\u0644" +} diff --git a/ui-electrobun/locales/en.json b/ui-electrobun/locales/en.json new file mode 100644 index 0000000..9745d4c --- /dev/null +++ b/ui-electrobun/locales/en.json @@ -0,0 +1,150 @@ +{ + "sidebar.settings": "Settings", + "sidebar.addGroup": "Add group", + "sidebar.addProject": "Add project", + "sidebar.groupName": "Group name", + "sidebar.notifications": "Notifications", + "sidebar.notifCount": "{count, plural, =0 {Notifications} one {{count} notification} other {{count} notifications}}", + "sidebar.close": "Close window", + "sidebar.maximize": "Maximize window", + "sidebar.minimize": "Minimize window", + + "agent.prompt.placeholder": "Ask a question or describe a task...", + "agent.prompt.send": "Send", + "agent.prompt.stop": "Stop agent", + "agent.status.running": "Running", + "agent.status.idle": "Idle", + "agent.status.done": "Done", + "agent.status.error": "Error", + "agent.status.stalled": "Stalled", + "agent.status.thinking": "Thinking", + "agent.tokens": "{count} tok", + "agent.toolCall": "Tool call", + "agent.toolResult": "Tool result", + "agent.contextMeter": "Context: {pct}%", + + "terminal.shell": "Shell", + "terminal.addTab": "New tab", + "terminal.closeTab": "Close tab", + "terminal.collapse": "Collapse", + "terminal.expand": "Expand", + + "settings.title": "Settings", + "settings.close": "Close settings", + "settings.appearance": "Appearance", + "settings.agents": "Agents", + "settings.security": "Security", + "settings.projects": "Projects", + "settings.orchestration": "Orchestration", + "settings.machines": "Machines", + "settings.keyboard": "Keyboard", + "settings.advanced": "Advanced", + "settings.marketplace": "Marketplace", + "settings.diagnostics": "Diagnostics", + "settings.theme": "Theme", + "settings.uiFont": "UI Font", + "settings.termFont": "Terminal Font", + "settings.termCursor": "Terminal Cursor", + "settings.scrollback": "Scrollback", + "settings.language": "Language", + "settings.editTheme": "Edit Theme", + "settings.customTheme": "+ Custom", + "settings.deleteTheme": "Delete theme", + "settings.cursorBlink": "Blink", + "settings.cursorOn": "On", + "settings.cursorOff": "Off", + "settings.scrollbackHint": "lines (100-100k)", + + "statusbar.running": "running", + "statusbar.idle": "idle", + "statusbar.stalled": "stalled", + "statusbar.attention": "attention", + "statusbar.needsAttention": "Needs attention", + "statusbar.burnRate": "Burn rate", + "statusbar.activeGroup": "Active group", + "statusbar.projects": "projects", + "statusbar.session": "session", + "statusbar.tokens": "tokens", + "statusbar.cost": "cost", + "statusbar.search": "Search (Ctrl+Shift+F)", + + "notifications.title": "Notifications", + "notifications.clearAll": "Clear all", + "notifications.noNotifications": "No notifications", + + "files.open": "Open", + "files.save": "Save", + "files.saving": "Saving...", + "files.modified": "Modified", + "files.tooLarge": "File too large to display", + "files.empty": "Empty file", + + "search.placeholder": "Search messages, tasks, comms...", + "search.noResults": "No results found", + "search.searching": "Searching...", + "search.resultsCount": "{count, plural, one {{count} result} other {{count} results}}", + + "comms.channels": "Channels", + "comms.directMessages": "Direct Messages", + "comms.sendMessage": "Send message", + "comms.placeholder": "Type a message...", + + "tasks.todo": "To Do", + "tasks.inProgress": "In Progress", + "tasks.review": "Review", + "tasks.done": "Done", + "tasks.blocked": "Blocked", + "tasks.addTask": "Add task", + "tasks.deleteTask": "Delete task", + "tasks.taskCount": "{count, plural, =0 {No tasks} one {{count} task} other {{count} tasks}}", + + "errors.connectionFailed": "Connection failed", + "errors.sessionExpired": "Session expired", + "errors.fileNotFound": "File not found", + "errors.unhandled": "Unhandled error: {message}", + "errors.generic": "Something went wrong", + + "splash.loading": "Loading...", + + "common.cancel": "Cancel", + "common.confirm": "Confirm", + "common.close": "Close", + "common.save": "Save", + "common.delete": "Delete", + "common.edit": "Edit", + "common.refresh": "Refresh", + "common.add": "Add", + "common.back": "Back", + "common.noItems": "No items", + + "project.name": "Project name", + "project.cwd": "Working directory (e.g. ~/code/myproject)", + "project.deleteConfirm": "Delete project \"{name}\"?", + "project.emptyGroup": "No projects in {group}", + "project.clone": "Clone", + "project.cloneBranch": "Branch name", + + "palette.title": "Command Palette", + "palette.placeholder": "Type a command...", + "palette.newTerminal": "New Terminal Tab", + "palette.openSettings": "Open Settings", + "palette.searchMessages": "Search Messages", + "palette.addProject": "Add Project", + "palette.clearAgent": "Clear Agent Context", + "palette.copyCost": "Copy Session Cost", + "palette.openDocs": "Open Documentation", + "palette.changeTheme": "Change Theme", + "palette.splitH": "Split Horizontally", + "palette.splitV": "Split Vertically", + "palette.focusNext": "Focus Next Project", + "palette.focusPrev": "Focus Previous Project", + "palette.closeTab": "Close Tab", + "palette.toggleTerminal": "Toggle Terminal", + "palette.reloadPlugins": "Reload Plugins", + "palette.toggleSidebar": "Toggle Sidebar", + "palette.zoomIn": "Zoom In", + "palette.zoomOut": "Zoom Out", + "palette.addProjectDesc": "Open a project directory", + "palette.clearAgentDesc": "Reset agent session", + "palette.changeThemeDesc": "Switch between 17 themes" +} diff --git a/ui-electrobun/locales/pl.json b/ui-electrobun/locales/pl.json new file mode 100644 index 0000000..efe2f71 --- /dev/null +++ b/ui-electrobun/locales/pl.json @@ -0,0 +1,150 @@ +{ + "sidebar.settings": "Ustawienia", + "sidebar.addGroup": "Dodaj grupe", + "sidebar.addProject": "Dodaj projekt", + "sidebar.groupName": "Nazwa grupy", + "sidebar.notifications": "Powiadomienia", + "sidebar.notifCount": "{count, plural, =0 {Powiadomienia} one {{count} powiadomienie} few {{count} powiadomienia} many {{count} powiadomien} other {{count} powiadomien}}", + "sidebar.close": "Zamknij okno", + "sidebar.maximize": "Maksymalizuj okno", + "sidebar.minimize": "Minimalizuj okno", + + "agent.prompt.placeholder": "Zadaj pytanie lub opisz zadanie...", + "agent.prompt.send": "Wyslij", + "agent.prompt.stop": "Zatrzymaj agenta", + "agent.status.running": "Dziala", + "agent.status.idle": "Bezczynny", + "agent.status.done": "Gotowe", + "agent.status.error": "Blad", + "agent.status.stalled": "Zawieszony", + "agent.status.thinking": "Mysli", + "agent.tokens": "{count} tok", + "agent.toolCall": "Wywolanie narzedzia", + "agent.toolResult": "Wynik narzedzia", + "agent.contextMeter": "Kontekst: {pct}%", + + "terminal.shell": "Powloka", + "terminal.addTab": "Nowa karta", + "terminal.closeTab": "Zamknij karte", + "terminal.collapse": "Zwin", + "terminal.expand": "Rozwin", + + "settings.title": "Ustawienia", + "settings.close": "Zamknij ustawienia", + "settings.appearance": "Wyglad", + "settings.agents": "Agenci", + "settings.security": "Bezpieczenstwo", + "settings.projects": "Projekty", + "settings.orchestration": "Orkiestracja", + "settings.machines": "Maszyny", + "settings.keyboard": "Klawiatura", + "settings.advanced": "Zaawansowane", + "settings.marketplace": "Sklep", + "settings.diagnostics": "Diagnostyka", + "settings.theme": "Motyw", + "settings.uiFont": "Czcionka UI", + "settings.termFont": "Czcionka terminala", + "settings.termCursor": "Kursor terminala", + "settings.scrollback": "Bufor przewijania", + "settings.language": "Jezyk", + "settings.editTheme": "Edytuj motyw", + "settings.customTheme": "+ Wlasny", + "settings.deleteTheme": "Usun motyw", + "settings.cursorBlink": "Mruganie", + "settings.cursorOn": "Wl.", + "settings.cursorOff": "Wyl.", + "settings.scrollbackHint": "linii (100-100k)", + + "statusbar.running": "dziala", + "statusbar.idle": "bezczynny", + "statusbar.stalled": "zawieszony", + "statusbar.attention": "uwaga", + "statusbar.needsAttention": "Wymaga uwagi", + "statusbar.burnRate": "Zuzycie", + "statusbar.activeGroup": "Aktywna grupa", + "statusbar.projects": "projekty", + "statusbar.session": "sesja", + "statusbar.tokens": "tokeny", + "statusbar.cost": "koszt", + "statusbar.search": "Szukaj (Ctrl+Shift+F)", + + "notifications.title": "Powiadomienia", + "notifications.clearAll": "Wyczysc wszystko", + "notifications.noNotifications": "Brak powiadomien", + + "files.open": "Otworz", + "files.save": "Zapisz", + "files.saving": "Zapisywanie...", + "files.modified": "Zmodyfikowany", + "files.tooLarge": "Plik za duzy do wyswietlenia", + "files.empty": "Pusty plik", + + "search.placeholder": "Szukaj wiadomosci, zadan, komunikacji...", + "search.noResults": "Brak wynikow", + "search.searching": "Szukanie...", + "search.resultsCount": "{count, plural, one {{count} wynik} few {{count} wyniki} many {{count} wynikow} other {{count} wynikow}}", + + "comms.channels": "Kanaly", + "comms.directMessages": "Wiadomosci prywatne", + "comms.sendMessage": "Wyslij wiadomosc", + "comms.placeholder": "Napisz wiadomosc...", + + "tasks.todo": "Do zrobienia", + "tasks.inProgress": "W toku", + "tasks.review": "Przeglad", + "tasks.done": "Gotowe", + "tasks.blocked": "Zablokowane", + "tasks.addTask": "Dodaj zadanie", + "tasks.deleteTask": "Usun zadanie", + "tasks.taskCount": "{count, plural, =0 {Brak zadan} one {{count} zadanie} few {{count} zadania} many {{count} zadan} other {{count} zadan}}", + + "errors.connectionFailed": "Polaczenie nieudane", + "errors.sessionExpired": "Sesja wygasla", + "errors.fileNotFound": "Nie znaleziono pliku", + "errors.unhandled": "Nieobsluzony blad: {message}", + "errors.generic": "Cos poszlo nie tak", + + "splash.loading": "Ladowanie...", + + "common.cancel": "Anuluj", + "common.confirm": "Potwierdz", + "common.close": "Zamknij", + "common.save": "Zapisz", + "common.delete": "Usun", + "common.edit": "Edytuj", + "common.refresh": "Odswiez", + "common.add": "Dodaj", + "common.back": "Wstecz", + "common.noItems": "Brak elementow", + + "project.name": "Nazwa projektu", + "project.cwd": "Katalog roboczy (np. ~/code/myproject)", + "project.deleteConfirm": "Usunac projekt \"{name}\"?", + "project.emptyGroup": "Brak projektow w {group}", + "project.clone": "Klonuj", + "project.cloneBranch": "Nazwa galezi", + + "palette.title": "Paleta polecen", + "palette.placeholder": "Wpisz polecenie...", + "palette.newTerminal": "Nowa karta terminala", + "palette.openSettings": "Otworz ustawienia", + "palette.searchMessages": "Szukaj wiadomosci", + "palette.addProject": "Dodaj projekt", + "palette.clearAgent": "Wyczysc kontekst agenta", + "palette.copyCost": "Kopiuj koszt sesji", + "palette.openDocs": "Otworz dokumentacje", + "palette.changeTheme": "Zmien motyw", + "palette.splitH": "Podziel poziomo", + "palette.splitV": "Podziel pionowo", + "palette.focusNext": "Nastepny projekt", + "palette.focusPrev": "Poprzedni projekt", + "palette.closeTab": "Zamknij karte", + "palette.toggleTerminal": "Przelacz terminal", + "palette.reloadPlugins": "Przeladuj wtyczki", + "palette.toggleSidebar": "Przelacz pasek boczny", + "palette.zoomIn": "Przybliz", + "palette.zoomOut": "Oddal", + "palette.addProjectDesc": "Otworz katalog projektu", + "palette.clearAgentDesc": "Zresetuj sesje agenta", + "palette.changeThemeDesc": "Wybierz sposrod 17 motywow" +} diff --git a/ui-electrobun/package.json b/ui-electrobun/package.json index 330c5ee..9048657 100644 --- a/ui-electrobun/package.json +++ b/ui-electrobun/package.json @@ -10,7 +10,8 @@ "hmr": "vite --port 9760", "build:canary": "vite build && electrobun build --env=canary", "test": "bun test src/bun/__tests__/", - "test:e2e": "wdio run tests/e2e/wdio.conf.js" + "test:e2e": "wdio run tests/e2e/wdio.conf.js", + "i18n:types": "bun scripts/i18n-types.ts" }, "dependencies": { "@codemirror/autocomplete": "^6.20.1", diff --git a/ui-electrobun/scripts/i18n-types.ts b/ui-electrobun/scripts/i18n-types.ts new file mode 100644 index 0000000..cb11c4c --- /dev/null +++ b/ui-electrobun/scripts/i18n-types.ts @@ -0,0 +1,37 @@ +#!/usr/bin/env bun +/** + * Reads locales/en.json and generates src/mainview/i18n.types.ts + * with a TranslationKey union type covering all keys. + * + * Usage: bun scripts/i18n-types.ts + */ + +import { readFileSync, writeFileSync } from 'fs'; +import { resolve } from 'path'; + +const ROOT = resolve(import.meta.dir, '..'); +const EN_PATH = resolve(ROOT, 'locales/en.json'); +const OUT_PATH = resolve(ROOT, 'src/mainview/i18n.types.ts'); + +const en: Record = JSON.parse(readFileSync(EN_PATH, 'utf-8')); +const keys = Object.keys(en).sort(); + +const lines: string[] = [ + '/**', + ' * Auto-generated by scripts/i18n-types.ts — do not edit manually.', + ' * Run: bun scripts/i18n-types.ts', + ' */', + '', + 'export type TranslationKey =', +]; + +keys.forEach((key, i) => { + const prefix = i === 0 ? ' | ' : ' | '; + const suffix = i === keys.length - 1 ? ';' : ''; + lines.push(`${prefix}'${key}'${suffix}`); +}); + +lines.push(''); + +writeFileSync(OUT_PATH, lines.join('\n'), 'utf-8'); +console.log(`[i18n-types] Generated ${keys.length} keys -> ${OUT_PATH}`); diff --git a/ui-electrobun/src/mainview/AgentPane.svelte b/ui-electrobun/src/mainview/AgentPane.svelte index 29e4e7d..e0d20b7 100644 --- a/ui-electrobun/src/mainview/AgentPane.svelte +++ b/ui-electrobun/src/mainview/AgentPane.svelte @@ -2,6 +2,7 @@ import { tick } from 'svelte'; import ChatInput from './ChatInput.svelte'; import type { AgentMessage, AgentStatus } from './agent-store.svelte.ts'; + import { t } from './i18n.svelte.ts'; interface Props { messages: AgentMessage[]; @@ -68,10 +69,10 @@ } function statusLabel(s: AgentStatus) { - if (s === 'running') return 'Running'; - if (s === 'error') return 'Error'; - if (s === 'done') return 'Done'; - return 'Idle'; + if (s === 'running') return t('agent.status.running'); + if (s === 'error') return t('agent.status.error'); + if (s === 'done') return t('agent.status.done'); + return t('agent.status.idle'); } function onResizeMouseDown(e: MouseEvent) { @@ -106,7 +107,7 @@ {fmtCost(costUsd)} {#if status === 'running' && onStop} -