diff --git a/docs/SETUP.md b/docs/SETUP.md index 80b0c40d..ee513c45 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -85,6 +85,10 @@ A non-empty `INTELLIGENCE_GATEWAY_WS_URL` takes precedence over `INTELLIGENCE_WS The model environment variable names follow the configured provider adapter. Provider credentials belong in `.env`, not client-side variables or source code. Conversation history lives in the configured Intelligence project; copying the SQLite file alone does not back up that history. +## Interface language + +The interface is available in English and Russian. It follows the browser language and can be changed in Settings under Appearance; the choice is saved in this browser. Translations live in `src/client/locales/`. To add a language, add a dictionary keyed by the English source strings and register it in `src/client/i18n.ts`. Messages that come from the server (for example API errors) stay in English. + ## Pages and page conversations Select a Space to open its page library. Search for a document, switch between grid and list views, or create a new page. The visual editor supports formatting, headings, lists, checklists, tables, and slash commands. Use `/` to insert a block and Cmd/Ctrl+S to save immediately. Pages autosave after editing pauses; the save status tells you whether changes reached the server. diff --git a/src/client/App.tsx b/src/client/App.tsx index e5cc6466..33e2c710 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -48,6 +48,7 @@ import { TaskRow } from './TaskPresentation'; import { TaskActions } from './TaskActions'; import { WorkspaceDialog, type Dialog } from './WorkspaceDialog'; import { submitComposerOnEnter } from './chat-composer'; +import { t, tp } from './i18n'; function describeFailure(error: unknown, fallback: string) { return { @@ -72,7 +73,10 @@ export function App() { dirtyPage.current = value; }, []); const setView = (next: typeof view) => { - if (dirtyPage.current && !window.confirm('Leave your unsaved page draft?')) + if ( + dirtyPage.current && + !window.confirm(t('Leave your unsaved page draft?')) + ) return; dirtyPage.current = false; if (next !== 'space') @@ -89,7 +93,7 @@ export function App() { if (dirtyPage.current && location.hash === acceptedHash) return; if ( dirtyPage.current && - !window.confirm('Leave your unsaved page draft?') + !window.confirm(t('Leave your unsaved page draft?')) ) { history.replaceState(null, '', acceptedHash || location.pathname); return; @@ -147,7 +151,7 @@ export function App() { setNotices((current) => applyRefreshResult( current, - describeFailure(e, 'Could not connect to the server.'), + describeFailure(e, t('Could not connect to the server.')), ), ); } @@ -173,7 +177,7 @@ export function App() { setNotices((current) => applyCaptureResult( current, - describeFailure(e, 'Could not connect to the server.'), + describeFailure(e, t('Could not connect to the server.')), ), ); }); @@ -193,7 +197,7 @@ export function App() { setTaskDetail(await api(`/tasks/${taskDetail.task.id}`)); return true; } catch (e) { - setError(e instanceof Error ? e.message : 'Could not save.'); + setError(e instanceof Error ? e.message : t('Could not save.')); return false; } }; @@ -231,7 +235,7 @@ export function App() { try { const next = await api('/conversations', 'POST', { dotId: dot.id, - title: text?.slice(0, 80) || 'A new thought', + title: text?.slice(0, 80) || t('A new thought'), }); await refresh(); setSelectedThread(next.id); @@ -241,7 +245,9 @@ export function App() { setMobile(false); } catch (e) { setError( - e instanceof Error ? e.message : 'Could not create the conversation.', + e instanceof Error + ? e.message + : t('Could not create the conversation.'), ); } finally { setBusy(false); @@ -251,9 +257,11 @@ export function App() { return (
-

Your own little corner.

+

{t('Your own little corner.')}

- Enter the owner access token configured on this template’s server. + {t( + 'Enter the owner access token configured on this template’s server.', + )}

{ @@ -267,48 +275,50 @@ export function App() { setError( err instanceof Error ? err.message - : 'Access token was not accepted.', + : t('Access token was not accepted.'), ); } }} > setAuth(e.target.value)} required /> - +
{error && (

{error}

)} -

The token stays in this tab’s session storage.

+

+ {t('The token stays in this tab’s session storage.')} +

); if (!state || !workspace || !dot) return (
-

Finding your dots…

+

{t('Finding your dots…')}

{error && ( <>

{error}

- + )}
); const content = (
-
- {configured ? 'SELF-HOSTED' : 'SETUP REQUIRED'} + {configured ? t('SELF-HOSTED') : t('SETUP REQUIRED')}
{!configured && ( @@ -660,20 +681,22 @@ export function App() {
- Connect your Dot + {t('Connect your Dot')}

- Connect your model and conversation service in - Settings to start chatting. Your Spaces and Dot - preferences are ready to use. + {t( + 'Connect your model and conversation service in Settings to start chatting. Your Spaces and Dot preferences are ready to use.', + )}

- Setup and usage metadata is collected by default.{' '} + {t( + 'Setup and usage metadata is collected by default.', + )}{' '} - Tracking and opt-out details + {t('Tracking and opt-out details')}

- Open the setup guide + {t('Open the setup guide')}
@@ -694,11 +717,11 @@ export function App() { }} >