From 695f7431fe6c7e9f29a2a9555157ecfa5ef2d0c5 Mon Sep 17 00:00:00 2001 From: Kalyel Nunes Laurindo Date: Thu, 8 Oct 2026 12:06:52 -0300 Subject: [PATCH 1/3] feat: add Brazilian Portuguese interface strings --- docs/LOCALIZATION.md | 7 + index.html | 4 +- src/client/App.tsx | 210 +++++----- src/client/CallView.tsx | 39 +- src/client/Chat.tsx | 79 ++-- src/client/ChatTranscript.tsx | 9 +- src/client/ComputerPanel.tsx | 205 +++++---- src/client/ComputerToolCard.tsx | 52 +-- src/client/ConnectionActionCard.tsx | 52 +-- src/client/ConnectionsSection.tsx | 58 ++- src/client/PageConversation.tsx | 31 +- src/client/PageDocument.tsx | 91 ++-- src/client/PageOutline.tsx | 9 +- src/client/PageReviewCard.tsx | 64 ++- src/client/ResultPane.tsx | 43 +- src/client/SpaceLibrary.tsx | 51 ++- src/client/SpaceNav.tsx | 10 +- src/client/SpaceWorkspace.tsx | 19 +- src/client/TaskActions.tsx | 17 +- src/client/TaskPresentation.tsx | 24 +- src/client/ThreadList.tsx | 15 +- src/client/WorkspaceDialog.tsx | 134 +++--- src/client/api.ts | 5 +- src/client/editor/DocumentMenu.tsx | 5 +- src/client/editor/RichEditor.tsx | 56 +-- src/client/editor/autosave.ts | 19 +- src/client/editor/markdown.ts | 12 +- src/client/editor/slash-commands.ts | 47 ++- src/client/i18n/pt-BR.ts | 630 ++++++++++++++++++++++++++++ src/client/page-context.ts | 6 +- src/client/page-review-decision.ts | 5 +- src/client/poll-notice.ts | 4 +- src/client/useVoice.ts | 29 +- 33 files changed, 1353 insertions(+), 688 deletions(-) create mode 100644 docs/LOCALIZATION.md create mode 100644 src/client/i18n/pt-BR.ts diff --git a/docs/LOCALIZATION.md b/docs/LOCALIZATION.md new file mode 100644 index 00000000..759b704b --- /dev/null +++ b/docs/LOCALIZATION.md @@ -0,0 +1,7 @@ +# Interface localization + +The interface currently ships in Brazilian Portuguese (`pt-BR`). Keep user-facing strings in `src/client/i18n/pt-BR.ts` and access them through the typed `t()` helper instead of adding translated copy directly to components. + +Translation keys are grouped by interface area (`nav`, `pages`, `tasks`, `chat`, and `dialogs`). Prefer short, descriptive keys and preserve product names, environment variable names, and technical identifiers. Format dates with the `pt-BR` locale. + +When adding another locale in the future, keep the same key structure so components can switch dictionaries without changing their copy. Update this document and the document language metadata when the default locale changes. diff --git a/index.html b/index.html index 3e37f29e..3bdd0ce2 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + @@ -7,7 +7,7 @@ OpenDots diff --git a/src/client/App.tsx b/src/client/App.tsx index e5cc6466..0f00f2b7 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -48,12 +48,18 @@ import { TaskRow } from './TaskPresentation'; import { TaskActions } from './TaskActions'; import { WorkspaceDialog, type Dialog } from './WorkspaceDialog'; import { submitComposerOnEnter } from './chat-composer'; +import { t } from './i18n/pt-BR'; function describeFailure(error: unknown, fallback: string) { return { ok: false as const, status: error instanceof ApiError ? error.status : undefined, - message: error instanceof Error ? error.message : fallback, + message: + error instanceof TypeError + ? fallback + : error instanceof Error + ? error.message + : fallback, }; } @@ -72,7 +78,7 @@ 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('workspace.unsavedPage'))) return; dirtyPage.current = false; if (next !== 'space') @@ -87,10 +93,7 @@ export function App() { ); if (!match) return; if (dirtyPage.current && location.hash === acceptedHash) return; - if ( - dirtyPage.current && - !window.confirm('Leave your unsaved page draft?') - ) { + if (dirtyPage.current && !window.confirm(t('workspace.unsavedPage'))) { history.replaceState(null, '', acceptedHash || location.pathname); return; } @@ -147,7 +150,7 @@ export function App() { setNotices((current) => applyRefreshResult( current, - describeFailure(e, 'Could not connect to the server.'), + describeFailure(e, t('notices.serverConnection')), ), ); } @@ -173,7 +176,7 @@ export function App() { setNotices((current) => applyCaptureResult( current, - describeFailure(e, 'Could not connect to the server.'), + describeFailure(e, t('notices.serverConnection')), ), ); }); @@ -193,7 +196,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('app.saveError')); return false; } }; @@ -231,7 +234,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('workspace.newIdea'), }); await refresh(); setSelectedThread(next.id); @@ -240,9 +243,7 @@ export function App() { setView('chat'); setMobile(false); } catch (e) { - setError( - e instanceof Error ? e.message : 'Could not create the conversation.', - ); + setError(e instanceof Error ? e.message : t('app.conversationError')); } finally { setBusy(false); } @@ -251,10 +252,8 @@ export function App() { return (
-

Your own little corner.

-

- Enter the owner access token configured on this template’s server. -

+

{t('workspace.yourCorner')}

+

{t('workspace.ownerToken')}

{ e.preventDefault(); @@ -264,51 +263,49 @@ export function App() { setError(''); await refresh(); } catch (err) { - setError( - err instanceof Error - ? err.message - : 'Access token was not accepted.', - ); + setError(err instanceof Error ? err.message : t('app.badToken')); } }} > setAuth(e.target.value)} required /> - +
{error && (

{error}

)} -

The token stays in this tab’s session storage.

+

{t('workspace.tokenSession')}

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

Finding your dots…

+

{t('workspace.findingAgents')}

{error && ( <>

{error}

- + )}
); const content = (
-
- {configured ? 'SELF-HOSTED' : 'SETUP REQUIRED'} + {configured ? t('app.localHosted') : t('app.setupRequired')}
)} {state.settings.paused && ( -
- All Dots are paused. Active compute stops and scheduled tasks wait. -
+
{t('workspace.pausedNotice')}
)} {view === 'space' ? ( - Edit specialist + {t('app.editSpecialist')} {!configured && ( @@ -660,20 +657,20 @@ export function App() {
- Connect your Dot + {t('app.connectAgent')}

- Connect your model and conversation service in - Settings to start chatting. Your Spaces and Dot - preferences are ready to use. + Conecte o modelo e o serviço de conversas em + Configurações para começar a conversar. Seus espaços e + preferências dos agentes continuam salvos.

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

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