From 433d044d65666db0ac28c3dff59fa06ba0d89820 Mon Sep 17 00:00:00 2001 From: Hansss <79178335+5Hyeons@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:34:54 +0900 Subject: [PATCH] fix(i18n): localize every contextual tour step (#12713) Only the automations and client-hosted-browser steps had an id-keyed entry in the localized step copy map, so every other contextual tour (workspace board, agent sessions, browser, tasks, floating workspace, workspace creation) rendered English titles, bodies, and action labels next to localized Back/Next/Done controls. Give every tour step a required stable id and move the copy lookup into contextual-tour-step-copy.ts. The map `satisfies` a record over all step ids, so a new step without localized copy is a type error. Primary and secondary action labels (Split terminal, Got it, Browser settings) are localized through the same entry. Existing automations and client-hosted-browser keys are unchanged so shipped translations keep resolving. Add the new strings to en and translate them for ko, es, ja, zh, fr, and pt. Co-authored-by: Neil --- ...ontextual-tour-overlay-measurement.test.ts | 29 +++ .../contextual-tour-overlay-measurement.ts | 72 ++---- .../contextual-tour-step-copy.ts | 213 ++++++++++++++++++ src/renderer/src/i18n/locales/en.json | 93 ++++++++ src/renderer/src/i18n/locales/es.json | 93 ++++++++ src/renderer/src/i18n/locales/fr.json | 93 ++++++++ src/renderer/src/i18n/locales/ja.json | 93 ++++++++ src/renderer/src/i18n/locales/ko.json | 93 ++++++++ src/renderer/src/i18n/locales/pt.json | 93 ++++++++ src/renderer/src/i18n/locales/zh.json | 93 ++++++++ src/shared/contextual-tours.ts | 19 +- 11 files changed, 931 insertions(+), 53 deletions(-) create mode 100644 src/renderer/src/components/contextual-tours/contextual-tour-step-copy.ts diff --git a/src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.test.ts b/src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.test.ts index 8c648531e59..1988845cda8 100644 --- a/src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.test.ts +++ b/src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.test.ts @@ -66,6 +66,34 @@ describe('contextual tour overlay measurement', () => { ) }) + it('localizes the title, shortcut body, and action label of non-automation steps', async () => { + await setRendererUiLanguage('ko') + const target = document.createElement('div') + target.setAttribute('data-contextual-tour-target', 'terminal-pane-split-target') + target.getBoundingClientRect = () => new DOMRect(0, 0, 20, 20) + document.body.appendChild(target) + + const result = measureContextualTourOverlayRenderState({ + tour: getContextualTour('workspace-agent-sessions'), + activeStepIndex: 0, + sidebarOpen: true, + keybindings: undefined, + previousTelemetryTotalSteps: 0 + }) + + expect(result.kind).toBe('render') + if (result.kind !== 'render') { + throw new Error(`Expected render result, received ${result.kind}`) + } + expect(result.renderState.title).toBe('터미널 창 분할') + expect(result.renderState.body).not.toContain('{terminal.splitRight}') + expect(result.renderState.body).toContain('두 번째 터미널 창을 열거나') + expect(result.renderState.primaryAction).toEqual({ + kind: 'split-terminal-pane', + label: '터미널 분할' + }) + }) + it('keeps localized copy on its own step when a step is inserted before it', async () => { await setRendererUiLanguage('ko') const target = document.createElement('button') @@ -79,6 +107,7 @@ describe('contextual tour overlay measurement', () => { ...automations, steps: [ { + id: 'inserted-step', title: 'Inserted step', body: 'Added ahead of the localized steps.', targetSelector: '[data-contextual-tour-target="automations-create"]' diff --git a/src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.ts b/src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.ts index b90145682f0..d1491b6f519 100644 --- a/src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.ts +++ b/src/renderer/src/components/contextual-tours/contextual-tour-overlay-measurement.ts @@ -1,5 +1,9 @@ import { formatShortcutLabel } from '@/hooks/useShortcutLabel' -import type { ContextualTour, ContextualTourId } from '../../../../shared/contextual-tours' +import type { + ContextualTour, + ContextualTourId, + ContextualTourStepAction +} from '../../../../shared/contextual-tours' import type { ContextualTourOutcome } from '../../../../shared/feature-education-telemetry' import { useAppStore } from '@/store' import { @@ -11,6 +15,7 @@ import { getVisibleContextualTourStepIndexes } from './contextual-tour-gate' import type { ActiveTourRenderState } from './ContextualTourOverlaySurface' +import { getLocalizedContextualTourStepCopy } from './contextual-tour-step-copy' import { translate } from '@/i18n/i18n' export type ContextualTourMeasurementAction = @@ -28,46 +33,6 @@ export type ContextualTourOverlayMeasurementResult = telemetryTotalSteps: number } -// Key translated thunks by stable step IDs so insertions cannot shift copy and language resolves at render time. -const LOCALIZED_STEP_COPY: Record string; body: () => string }> = { - 'automations-intro': { - title: () => - translate( - 'auto.components.contextual.tours.contextual.tour.overlay.measurement.automations.intro.title', - 'What is an automation?' - ), - body: () => - translate( - 'auto.components.contextual.tours.contextual.tour.overlay.measurement.automations.intro.body', - 'Automations run agent work on a schedule. Add an automation by clicking this button.' - ) - }, - 'client-hosted-browser-intro': { - title: () => - translate( - 'auto.components.contextual.tours.contextual.tour.overlay.measurement.client.hosted.browser.intro.title', - 'This page renders on your desktop' - ), - body: () => - translate( - 'auto.components.contextual.tours.contextual.tour.overlay.measurement.client.hosted.browser.intro.body', - 'Remote browser tabs now render on this device. Network traffic still goes through the remote host.' - ) - }, - 'automations-results': { - title: () => - translate( - 'auto.components.contextual.tours.contextual.tour.overlay.measurement.automations.results.title', - 'Find the results' - ), - body: () => - translate( - 'auto.components.contextual.tours.contextual.tour.overlay.measurement.automations.results.body', - 'Runs show when automations ran, what happened, and where to inspect their output.' - ) - } -} - export function getContextualTourDisplayProgress(args: { tour: ContextualTour visibleStepIndexes: readonly number[] @@ -128,13 +93,6 @@ export function measureContextualTourOverlayRenderState(args: { ) const activeStep = args.tour.steps[args.activeStepIndex] const target = activeStep ? getMeasurableContextualTourTarget(activeStep.targetSelector) : null - const localizedCopy = activeStep?.id ? LOCALIZED_STEP_COPY[activeStep.id] : undefined - const localizedTitle = localizedCopy ? localizedCopy.title() : activeStep?.title - const localizedBody = localizedCopy - ? localizedCopy.body() - : activeStep - ? getContextualTourStepCopy(activeStep) - : undefined const progress = getContextualTourDisplayProgress({ tour: args.tour, visibleStepIndexes, @@ -161,6 +119,7 @@ export function measureContextualTourOverlayRenderState(args: { return { kind: 'cancel' } } + const localizedCopy = getLocalizedContextualTourStepCopy(activeStep.id) const sidebarAlreadyVisible = activeStep.primaryAction?.kind === 'show-worktrees' && args.sidebarOpen const primaryAction = sidebarAlreadyVisible @@ -171,8 +130,10 @@ export function measureContextualTourOverlayRenderState(args: { 'Next' ) } as const) - : activeStep.primaryAction - const secondaryAction = sidebarAlreadyVisible ? undefined : activeStep.secondaryAction + : withLocalizedLabel(activeStep.primaryAction, localizedCopy?.primaryActionLabel) + const secondaryAction = sidebarAlreadyVisible + ? undefined + : withLocalizedLabel(activeStep.secondaryAction, localizedCopy?.secondaryActionLabel) return { kind: 'render', @@ -181,9 +142,9 @@ export function measureContextualTourOverlayRenderState(args: { rect: target.rect, targetElement: target.element, progress, - title: localizedTitle ?? activeStep.title, + title: localizedCopy?.title ?? activeStep.title, body: formatContextualTourStepCopy( - localizedBody ?? getContextualTourStepCopy(activeStep), + localizedCopy?.body ?? getContextualTourStepCopy(activeStep), args.keybindings ), control: activeStep.control, @@ -269,6 +230,13 @@ export function getContextualTourCleanupOutcome( : 'cancelled' } +function withLocalizedLabel( + action: ContextualTourStepAction | undefined, + label: string | undefined +): ContextualTourStepAction | undefined { + return action && label ? { ...action, label } : action +} + function formatContextualTourStepCopy( copy: string, keybindings: Parameters[1] diff --git a/src/renderer/src/components/contextual-tours/contextual-tour-step-copy.ts b/src/renderer/src/components/contextual-tours/contextual-tour-step-copy.ts new file mode 100644 index 00000000000..5b61a4e6205 --- /dev/null +++ b/src/renderer/src/components/contextual-tours/contextual-tour-step-copy.ts @@ -0,0 +1,213 @@ +import { translate } from '@/i18n/i18n' +import type { ContextualTourStepId } from '../../../../shared/contextual-tours' + +export type LocalizedContextualTourStepCopy = { + title: string + body: string + primaryActionLabel?: string + secondaryActionLabel?: string +} + +type StepCopyThunk = () => LocalizedContextualTourStepCopy + +// Thunks resolve translate() in the language active at render time; `satisfies` makes a step without copy a type error. +const LOCALIZED_STEP_COPY: Partial> = { + 'workspace-board-plan': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.workspace.board.plan.title', + 'Plan work on the board' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.workspace.board.plan.body', + 'Use the board when you want to see workspaces by status instead of by project.' + ) + }), + 'workspace-board-lanes': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.workspace.board.lanes.title', + 'Move work through lanes' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.workspace.board.lanes.body', + 'Drag workspaces between lanes as their status changes.' + ) + }), + 'agent-sessions-split': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.agent.sessions.split.title', + 'Split a terminal pane' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.agent.sessions.split.body', + 'Open a second terminal pane with {terminal.splitRight}, or right-click the pane for split options.' + ), + primaryActionLabel: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.agent.sessions.split.action', + 'Split terminal' + ) + }), + 'agent-sessions-parallel': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.agent.sessions.parallel.title', + 'Start another task in parallel' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.agent.sessions.parallel.body', + 'Each worktree gets its own branch, so parallel work stays separate.' + ) + }), + 'browser-grab': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.browser.grab.title', + 'Grab page context for agents' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.browser.grab.body', + "Use the grab tool to copy a page element's context for agents." + ) + }), + 'browser-annotate': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.browser.annotate.title', + 'Mark design feedback in place' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.browser.annotate.body', + 'Annotate elements and send those notes to an agent.' + ) + }), + 'browser-logins': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.browser.logins.title', + 'Stay logged in' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.browser.logins.body', + 'Bring your existing logins into Orca to stay signed in immediately.' + ) + }), + 'client-hosted-browser-intro': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.overlay.measurement.client.hosted.browser.intro.title', + 'This page renders on your desktop' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.overlay.measurement.client.hosted.browser.intro.body', + 'Remote browser tabs now render on this device. Network traffic still goes through the remote host.' + ), + primaryActionLabel: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.client.hosted.browser.intro.action', + 'Got it' + ), + secondaryActionLabel: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.client.hosted.browser.intro.secondaryAction', + 'Browser settings' + ) + }), + 'tasks-source': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.tasks.source.title', + 'Choose the work source' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.tasks.source.body', + 'Switch between connected providers and project filters without changing pages.' + ) + }), + 'tasks-filter': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.tasks.filter.title', + 'Filter to the work you need' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.tasks.filter.body', + 'Use presets and search to narrow issues, reviews, merge requests, or tasks.' + ) + }), + 'tasks-start': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.tasks.start.title', + 'Start from work items' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.tasks.start.body', + 'Use Start or Open on a task, issue, review, or merge request to bring its context into a workspace.' + ) + }), + 'automations-intro': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.overlay.measurement.automations.intro.title', + 'What is an automation?' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.overlay.measurement.automations.intro.body', + 'Automations run agent work on a schedule. Add an automation by clicking this button.' + ) + }), + 'automations-results': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.overlay.measurement.automations.results.title', + 'Find the results' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.overlay.measurement.automations.results.body', + 'Runs show when automations ran, what happened, and where to inspect their output.' + ) + }), + 'floating-workspace-repos': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.floating.workspace.repos.title', + 'Run an agent across every repo' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.floating.workspace.repos.body', + 'Agents here run in any folder you choose. Point one at the directory above your services to work across all your repos at once.' + ) + }), + 'floating-workspace-scratchpad': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.floating.workspace.scratchpad.title', + 'Or use it as a scratchpad' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.floating.workspace.scratchpad.body', + 'Open agents, scratch terminals, notes, and browser tabs without cluttering the worktree you’re focused on.' + ) + }), + 'workspace-creation-project': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.workspace.creation.project.title', + 'Pick a project' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.workspace.creation.project.body', + 'Orca isolates each task in its own worktree, branched off your base.' + ) + }), + 'workspace-creation-name': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.workspace.creation.name.title', + 'Name it, or start from existing work' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.workspace.creation.name.body', + 'Start from a linked task for a short issue or PR name. Or leave it blank to auto-name it from your first agent message.' + ) + }), + 'workspace-creation-agent': () => ({ + title: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.workspace.creation.agent.title', + 'Choose what agent starts the work' + ), + body: translate( + 'auto.components.contextual.tours.contextual.tour.step.copy.workspace.creation.agent.body', + 'Pick the agent that should be opened when this worktree is created.' + ) + }) +} satisfies Record + +export function getLocalizedContextualTourStepCopy( + stepId: string +): LocalizedContextualTourStepCopy | undefined { + return LOCALIZED_STEP_COPY[stepId]?.() +} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 4f35129944d..ca469b3b9d7 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -17259,6 +17259,99 @@ } } } + }, + "step": { + "copy": { + "workspace": { + "board": { + "plan": { + "title": "Plan work on the board", + "body": "Use the board when you want to see workspaces by status instead of by project." + }, + "lanes": { + "title": "Move work through lanes", + "body": "Drag workspaces between lanes as their status changes." + } + }, + "creation": { + "project": { + "title": "Pick a project", + "body": "Orca isolates each task in its own worktree, branched off your base." + }, + "name": { + "title": "Name it, or start from existing work", + "body": "Start from a linked task for a short issue or PR name. Or leave it blank to auto-name it from your first agent message." + }, + "agent": { + "title": "Choose what agent starts the work", + "body": "Pick the agent that should be opened when this worktree is created." + } + } + }, + "agent": { + "sessions": { + "split": { + "title": "Split a terminal pane", + "body": "Open a second terminal pane with {terminal.splitRight}, or right-click the pane for split options.", + "action": "Split terminal" + }, + "parallel": { + "title": "Start another task in parallel", + "body": "Each worktree gets its own branch, so parallel work stays separate." + } + } + }, + "browser": { + "grab": { + "title": "Grab page context for agents", + "body": "Use the grab tool to copy a page element's context for agents." + }, + "annotate": { + "title": "Mark design feedback in place", + "body": "Annotate elements and send those notes to an agent." + }, + "logins": { + "title": "Stay logged in", + "body": "Bring your existing logins into Orca to stay signed in immediately." + } + }, + "tasks": { + "source": { + "title": "Choose the work source", + "body": "Switch between connected providers and project filters without changing pages." + }, + "filter": { + "title": "Filter to the work you need", + "body": "Use presets and search to narrow issues, reviews, merge requests, or tasks." + }, + "start": { + "title": "Start from work items", + "body": "Use Start or Open on a task, issue, review, or merge request to bring its context into a workspace." + } + }, + "floating": { + "workspace": { + "repos": { + "title": "Run an agent across every repo", + "body": "Agents here run in any folder you choose. Point one at the directory above your services to work across all your repos at once." + }, + "scratchpad": { + "title": "Or use it as a scratchpad", + "body": "Open agents, scratch terminals, notes, and browser tabs without cluttering the worktree you’re focused on." + } + } + }, + "client": { + "hosted": { + "browser": { + "intro": { + "action": "Got it", + "secondaryAction": "Browser settings" + } + } + } + } + } } } } diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 5b52b72d8b8..8ce02f0e3ed 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -16830,6 +16830,99 @@ } } } + }, + "step": { + "copy": { + "workspace": { + "board": { + "plan": { + "title": "Planifica el trabajo en el tablero", + "body": "Usa el tablero cuando quieras ver los espacios de trabajo por estado en lugar de por proyecto." + }, + "lanes": { + "title": "Mueve el trabajo entre carriles", + "body": "Arrastra los espacios de trabajo entre carriles a medida que cambia su estado." + } + }, + "creation": { + "project": { + "title": "Elige un proyecto", + "body": "Orca aísla cada tarea en su propio worktree, creado a partir de tu rama base." + }, + "name": { + "title": "Ponle nombre o usa un elemento de trabajo existente", + "body": "Parte de una tarea vinculada para usar un nombre corto de incidencia o PR. O déjalo en blanco para nombrarlo automáticamente a partir de tu primer mensaje al agente." + }, + "agent": { + "title": "Elige qué agente empieza el trabajo", + "body": "Elige el agente que debe abrirse cuando se cree este worktree." + } + } + }, + "agent": { + "sessions": { + "split": { + "title": "Divide un panel de terminal", + "body": "Abre un segundo panel de terminal con {terminal.splitRight} o haz clic derecho en el panel para ver las opciones de división.", + "action": "Dividir terminal" + }, + "parallel": { + "title": "Empieza otra tarea en paralelo", + "body": "Cada worktree tiene su propia rama, así que el trabajo en paralelo se mantiene separado." + } + } + }, + "browser": { + "grab": { + "title": "Captura el contexto de la página para los agentes", + "body": "Usa la herramienta de captura para copiar el contexto de un elemento de la página para los agentes." + }, + "annotate": { + "title": "Marca el feedback de diseño sobre la página", + "body": "Anota elementos y envía esas notas a un agente." + }, + "logins": { + "title": "Mantén la sesión iniciada", + "body": "Lleva tus inicios de sesión existentes a Orca para seguir conectado de inmediato." + } + }, + "tasks": { + "source": { + "title": "Elige el origen del trabajo", + "body": "Cambia entre proveedores conectados y filtros de proyecto sin cambiar de página." + }, + "filter": { + "title": "Filtra hasta el trabajo que necesitas", + "body": "Usa los preajustes y la búsqueda para acotar incidencias, revisiones, merge requests o tareas." + }, + "start": { + "title": "Empieza desde los elementos de trabajo", + "body": "Usa Iniciar o Abrir en una tarea, incidencia, revisión o merge request para llevar su contexto a un espacio de trabajo." + } + }, + "floating": { + "workspace": { + "repos": { + "title": "Ejecuta un agente en todos los repositorios", + "body": "Los agentes de aquí se ejecutan en la carpeta que elijas. Apunta uno al directorio superior a tus servicios para trabajar en todos tus repositorios a la vez." + }, + "scratchpad": { + "title": "O úsalo como bloc de notas", + "body": "Abre agentes, terminales temporales, notas y pestañas del navegador sin saturar el worktree en el que estás concentrado." + } + } + }, + "client": { + "hosted": { + "browser": { + "intro": { + "action": "Entendido", + "secondaryAction": "Configuración del navegador" + } + } + } + } + } } } } diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json index 8406e85efa7..7e28695bf2f 100644 --- a/src/renderer/src/i18n/locales/fr.json +++ b/src/renderer/src/i18n/locales/fr.json @@ -16975,6 +16975,99 @@ } } } + }, + "step": { + "copy": { + "workspace": { + "board": { + "plan": { + "title": "Planifiez le travail sur le tableau", + "body": "Utilisez le tableau pour voir les espaces de travail par statut plutôt que par projet." + }, + "lanes": { + "title": "Faites avancer le travail entre les colonnes", + "body": "Faites glisser les espaces de travail d’une colonne à l’autre à mesure que leur statut change." + } + }, + "creation": { + "project": { + "title": "Choisissez un projet", + "body": "Orca isole chaque tâche dans son propre worktree, créé à partir de votre branche de base." + }, + "name": { + "title": "Nommez-le, ou partez d’un travail existant", + "body": "Partez d’une tâche liée pour obtenir un nom court d’issue ou de PR. Ou laissez le champ vide pour le nommer automatiquement à partir de votre premier message à l’agent." + }, + "agent": { + "title": "Choisissez l’agent qui démarre le travail", + "body": "Choisissez l’agent à ouvrir lors de la création de ce worktree." + } + } + }, + "agent": { + "sessions": { + "split": { + "title": "Scindez un volet de terminal", + "body": "Ouvrez un second volet de terminal avec {terminal.splitRight}, ou faites un clic droit sur le volet pour afficher les options de division.", + "action": "Scinder le terminal" + }, + "parallel": { + "title": "Lancez une autre tâche en parallèle", + "body": "Chaque worktree a sa propre branche, ce qui garde le travail en parallèle séparé." + } + } + }, + "browser": { + "grab": { + "title": "Récupérez le contexte de la page pour les agents", + "body": "Utilisez l’outil de capture pour copier le contexte d’un élément de la page pour les agents." + }, + "annotate": { + "title": "Annotez vos retours de design sur place", + "body": "Annotez des éléments et envoyez ces notes à un agent." + }, + "logins": { + "title": "Restez connecté", + "body": "Importez vos connexions existantes dans Orca pour rester connecté immédiatement." + } + }, + "tasks": { + "source": { + "title": "Choisissez la source du travail", + "body": "Passez d’un fournisseur connecté ou d’un filtre de projet à l’autre sans changer de page." + }, + "filter": { + "title": "Filtrez le travail dont vous avez besoin", + "body": "Utilisez les préréglages et la recherche pour affiner les issues, revues, merge requests ou tâches." + }, + "start": { + "title": "Partez des éléments de travail", + "body": "Utilisez Démarrer ou Ouvrir sur une tâche, une issue, une revue ou une merge request pour importer son contexte dans un espace de travail." + } + }, + "floating": { + "workspace": { + "repos": { + "title": "Lancez un agent sur tous vos dépôts", + "body": "Ici, les agents s’exécutent dans le dossier de votre choix. Pointez-en un vers le dossier parent de vos services pour travailler sur tous vos dépôts à la fois." + }, + "scratchpad": { + "title": "Ou utilisez-le comme brouillon", + "body": "Ouvrez des agents, des terminaux temporaires, des notes et des onglets de navigateur sans encombrer le worktree sur lequel vous êtes concentré." + } + } + }, + "client": { + "hosted": { + "browser": { + "intro": { + "action": "Compris", + "secondaryAction": "Paramètres du navigateur" + } + } + } + } + } } } } diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 31c0a7f44f2..f7071bf6051 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -16814,6 +16814,99 @@ } } } + }, + "step": { + "copy": { + "workspace": { + "board": { + "plan": { + "title": "ボードで作業を計画する", + "body": "プロジェクト別ではなく状態別にワークスペースを見たいときはボードを使います。" + }, + "lanes": { + "title": "レーン間で作業を動かす", + "body": "状態が変わったら、ワークスペースをレーン間にドラッグします。" + } + }, + "creation": { + "project": { + "title": "プロジェクトを選ぶ", + "body": "Orca は各タスクをベースブランチから分岐した専用のワークツリーに分離します。" + }, + "name": { + "title": "名前を付けるか、既存の作業から始める", + "body": "リンクされたタスクから始めると、短い issue 名や PR 名が使われます。空欄のままにすると、最初の Agent メッセージから自動で名前が付きます。" + }, + "agent": { + "title": "作業を開始する Agent を選ぶ", + "body": "このワークツリーが作成されたときに開く Agent を選びます。" + } + } + }, + "agent": { + "sessions": { + "split": { + "title": "ターミナルペインを分割する", + "body": "{terminal.splitRight} で 2 つ目のターミナルペインを開くか、ペインを右クリックして分割オプションを表示します。", + "action": "ターミナルを分割" + }, + "parallel": { + "title": "別のタスクを並行して始める", + "body": "ワークツリーごとに専用のブランチがあるため、並行して進める作業が混ざりません。" + } + } + }, + "browser": { + "grab": { + "title": "Agent 向けにページのコンテキストを取得する", + "body": "grab ツールでページ要素のコンテキストをコピーして Agent に渡します。" + }, + "annotate": { + "title": "デザインのフィードバックをその場で残す", + "body": "要素に注釈を付けて、そのメモを Agent に送ります。" + }, + "logins": { + "title": "ログイン状態を保つ", + "body": "既存のログイン情報を Orca に取り込んで、すぐにログイン状態を保ちます。" + } + }, + "tasks": { + "source": { + "title": "作業のソースを選ぶ", + "body": "ページを移動せずに、接続済みのプロバイダーとプロジェクトフィルターを切り替えます。" + }, + "filter": { + "title": "必要な作業だけに絞り込む", + "body": "プリセットと検索で issues、レビュー、merge requests、タスクを絞り込みます。" + }, + "start": { + "title": "作業アイテムから始める", + "body": "タスク、issue、レビュー、merge request で開始または開くを押すと、そのコンテキストをワークスペースに取り込めます。" + } + }, + "floating": { + "workspace": { + "repos": { + "title": "すべてのリポジトリで Agent を実行する", + "body": "ここの Agent は選んだ任意のフォルダーで実行されます。サービスの 1 つ上のディレクトリを指定すれば、すべてのリポジトリをまとめて扱えます。" + }, + "scratchpad": { + "title": "スクラッチパッドとしても使える", + "body": "集中しているワークツリーを散らかさずに、Agent、一時的なターミナル、ノート、ブラウザタブを開けます。" + } + } + }, + "client": { + "hosted": { + "browser": { + "intro": { + "action": "了解", + "secondaryAction": "ブラウザ設定" + } + } + } + } + } } } } diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index c5b35d0ad9e..5b2cad126d4 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -16814,6 +16814,99 @@ } } } + }, + "step": { + "copy": { + "workspace": { + "board": { + "plan": { + "title": "보드에서 작업 계획하기", + "body": "프로젝트별이 아니라 상태별로 워크스페이스를 보고 싶을 때 보드를 사용하세요." + }, + "lanes": { + "title": "레인을 따라 작업 옮기기", + "body": "상태가 바뀌면 워크스페이스를 레인 사이로 끌어다 놓으세요." + } + }, + "creation": { + "project": { + "title": "프로젝트 선택", + "body": "Orca는 각 작업을 기준 브랜치에서 만든 자체 워크트리로 분리합니다." + }, + "name": { + "title": "이름을 정하거나 기존 작업에서 시작하기", + "body": "연결된 작업에서 시작하면 짧은 이슈나 PR 이름을 사용합니다. 비워 두면 첫 agent 메시지에서 이름이 자동으로 지정됩니다." + }, + "agent": { + "title": "작업을 시작할 agent 선택", + "body": "이 워크트리가 생성될 때 열릴 agent를 선택하세요." + } + } + }, + "agent": { + "sessions": { + "split": { + "title": "터미널 창 분할", + "body": "{terminal.splitRight} 키로 두 번째 터미널 창을 열거나, 창을 우클릭해 분할 옵션을 확인하세요.", + "action": "터미널 분할" + }, + "parallel": { + "title": "다른 작업을 병렬로 시작하기", + "body": "워크트리마다 자체 브랜치를 가지므로 병렬로 진행하는 작업이 서로 섞이지 않습니다." + } + } + }, + "browser": { + "grab": { + "title": "agent를 위한 페이지 컨텍스트 가져오기", + "body": "grab 도구로 페이지 요소의 컨텍스트를 복사해 agent에 전달하세요." + }, + "annotate": { + "title": "디자인 피드백을 화면에 바로 남기기", + "body": "요소에 주석을 달고 그 메모를 agent에 보내세요." + }, + "logins": { + "title": "로그인 상태 유지", + "body": "기존 로그인 정보를 Orca로 가져와 바로 로그인 상태를 유지하세요." + } + }, + "tasks": { + "source": { + "title": "작업 소스 선택", + "body": "페이지를 이동하지 않고 연결된 제공자와 프로젝트 필터를 전환하세요." + }, + "filter": { + "title": "필요한 작업만 필터링하기", + "body": "프리셋과 검색으로 이슈, 리뷰, merge requests, 작업의 범위를 좁히세요." + }, + "start": { + "title": "작업 항목에서 시작하기", + "body": "작업, 이슈, 리뷰, merge request에서 시작 또는 열기를 눌러 해당 컨텍스트를 워크스페이스로 가져오세요." + } + }, + "floating": { + "workspace": { + "repos": { + "title": "agent 하나로 모든 repo 다루기", + "body": "이곳의 agent는 선택한 어떤 폴더에서도 실행됩니다. 서비스들의 상위 디렉터리를 지정하면 모든 repo에서 한 번에 작업할 수 있습니다." + }, + "scratchpad": { + "title": "메모장처럼 사용하기", + "body": "집중하고 있는 워크트리를 어지럽히지 않고 agent, 임시 터미널, 노트, 브라우저 탭을 열 수 있습니다." + } + } + }, + "client": { + "hosted": { + "browser": { + "intro": { + "action": "확인", + "secondaryAction": "브라우저 설정" + } + } + } + } + } } } } diff --git a/src/renderer/src/i18n/locales/pt.json b/src/renderer/src/i18n/locales/pt.json index 4218c93441c..c5b8673b604 100644 --- a/src/renderer/src/i18n/locales/pt.json +++ b/src/renderer/src/i18n/locales/pt.json @@ -18518,6 +18518,99 @@ } } } + }, + "step": { + "copy": { + "workspace": { + "board": { + "plan": { + "title": "Planeje o trabalho no quadro", + "body": "Use o quadro quando quiser ver os espaços de trabalho por status em vez de por projeto." + }, + "lanes": { + "title": "Mova o trabalho entre as colunas", + "body": "Arraste os espaços de trabalho entre as colunas conforme o status deles muda." + } + }, + "creation": { + "project": { + "title": "Escolha um projeto", + "body": "O Orca isola cada tarefa em seu próprio worktree, criado a partir da sua branch base." + }, + "name": { + "title": "Dê um nome ou comece de um trabalho existente", + "body": "Comece de uma tarefa vinculada para usar um nome curto de issue ou PR. Ou deixe em branco para nomeá-lo automaticamente a partir da sua primeira mensagem ao agente." + }, + "agent": { + "title": "Escolha qual agente inicia o trabalho", + "body": "Escolha o agente que deve ser aberto quando este worktree for criado." + } + } + }, + "agent": { + "sessions": { + "split": { + "title": "Divida um painel de terminal", + "body": "Abra um segundo painel de terminal com {terminal.splitRight} ou clique com o botão direito no painel para ver as opções de divisão.", + "action": "Dividir terminal" + }, + "parallel": { + "title": "Comece outra tarefa em paralelo", + "body": "Cada worktree tem sua própria branch, então o trabalho em paralelo fica separado." + } + } + }, + "browser": { + "grab": { + "title": "Capture o contexto da página para os agentes", + "body": "Use a ferramenta de captura para copiar o contexto de um elemento da página para os agentes." + }, + "annotate": { + "title": "Marque o feedback de design no lugar", + "body": "Anote elementos e envie essas notas para um agente." + }, + "logins": { + "title": "Continue conectado", + "body": "Traga seus logins existentes para o Orca e continue conectado imediatamente." + } + }, + "tasks": { + "source": { + "title": "Escolha a origem do trabalho", + "body": "Alterne entre provedores conectados e filtros de projeto sem mudar de página." + }, + "filter": { + "title": "Filtre o trabalho de que você precisa", + "body": "Use predefinições e a busca para restringir issues, revisões, merge requests ou tarefas." + }, + "start": { + "title": "Comece a partir dos itens de trabalho", + "body": "Use Iniciar ou Abrir em uma tarefa, issue, revisão ou merge request para levar o contexto dela a um espaço de trabalho." + } + }, + "floating": { + "workspace": { + "repos": { + "title": "Execute um agente em todos os repositórios", + "body": "Os agentes aqui são executados em qualquer pasta que você escolher. Aponte um para o diretório acima dos seus serviços para trabalhar em todos os seus repositórios de uma vez." + }, + "scratchpad": { + "title": "Ou use como rascunho", + "body": "Abra agentes, terminais temporários, notas e abas do navegador sem bagunçar o worktree em que você está focado." + } + } + }, + "client": { + "hosted": { + "browser": { + "intro": { + "action": "Entendi", + "secondaryAction": "Configurações do navegador" + } + } + } + } + } } } } diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 2de5651e6e9..cb8451e0f8a 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -16814,6 +16814,99 @@ } } } + }, + "step": { + "copy": { + "workspace": { + "board": { + "plan": { + "title": "在看板中规划工作", + "body": "想按状态而不是按项目查看工作区时,请使用看板。" + }, + "lanes": { + "title": "在泳道之间移动工作", + "body": "状态变化时,把工作区拖到相应的泳道。" + } + }, + "creation": { + "project": { + "title": "选择项目", + "body": "Orca 会把每个任务隔离到基于基础分支创建的独立工作树中。" + }, + "name": { + "title": "命名,或从已有工作开始", + "body": "从关联的任务开始可获得简短的 issue 或 PR 名称。留空则会根据你的第一条智能体消息自动命名。" + }, + "agent": { + "title": "选择由哪个智能体开始工作", + "body": "选择创建此工作树时要打开的智能体。" + } + } + }, + "agent": { + "sessions": { + "split": { + "title": "拆分终端窗格", + "body": "使用 {terminal.splitRight} 打开第二个终端窗格,或右键点击窗格查看拆分选项。", + "action": "拆分终端" + }, + "parallel": { + "title": "并行开始另一个任务", + "body": "每个工作树都有自己的分支,因此并行进行的工作互不干扰。" + } + } + }, + "browser": { + "grab": { + "title": "为智能体抓取页面上下文", + "body": "使用抓取工具复制页面元素的上下文,供智能体使用。" + }, + "annotate": { + "title": "就地标注设计反馈", + "body": "为元素添加批注,并把这些备注发送给智能体。" + }, + "logins": { + "title": "保持登录状态", + "body": "把现有登录信息导入 Orca,立即保持登录状态。" + } + }, + "tasks": { + "source": { + "title": "选择工作来源", + "body": "无需切换页面即可在已连接的提供方和项目筛选之间切换。" + }, + "filter": { + "title": "筛选出你需要的工作", + "body": "使用预设和搜索来缩小 issues、评审、merge requests 或任务的范围。" + }, + "start": { + "title": "从工作项开始", + "body": "在任务、issue、评审或 merge request 上点击开始或打开,即可把其上下文带入工作区。" + } + }, + "floating": { + "workspace": { + "repos": { + "title": "在所有存储库中运行智能体", + "body": "这里的智能体可在你选择的任意文件夹中运行。把它指向服务所在目录的上一级,即可一次性处理所有存储库。" + }, + "scratchpad": { + "title": "也可当作草稿区使用", + "body": "打开智能体、临时终端、笔记和浏览器标签页,同时不会弄乱你正在专注的工作树。" + } + } + }, + "client": { + "hosted": { + "browser": { + "intro": { + "action": "知道了", + "secondaryAction": "浏览器设置" + } + } + } + } + } } } } diff --git a/src/shared/contextual-tours.ts b/src/shared/contextual-tours.ts index 77519a6b8cf..10e0876598a 100644 --- a/src/shared/contextual-tours.ts +++ b/src/shared/contextual-tours.ts @@ -33,7 +33,7 @@ export type ContextualTourStepPlacement = 'top' | 'right' | 'bottom' | 'left' export type ContextualTourStep = { // Stable anchor for localized copy — position-keyed translations shift onto the wrong step when one is inserted. - id?: string + id: string title: string body: string targetSelector: string @@ -59,6 +59,7 @@ export const CONTEXTUAL_TOURS = [ id: 'workspace-board', steps: [ { + id: 'workspace-board-plan', title: 'Plan work on the board', body: 'Use the board when you want to see workspaces by status instead of by project.', targetSelector: '[data-contextual-tour-target="workspace-board-center"]', @@ -66,6 +67,7 @@ export const CONTEXTUAL_TOURS = [ preferredPlacement: 'bottom' }, { + id: 'workspace-board-lanes', title: 'Move work through lanes', body: 'Drag workspaces between lanes as their status changes.', targetSelector: @@ -77,6 +79,7 @@ export const CONTEXTUAL_TOURS = [ id: 'workspace-agent-sessions', steps: [ { + id: 'agent-sessions-split', title: 'Split a terminal pane', body: 'Open a second terminal pane with {terminal.splitRight}, or right-click the pane for split options.', targetSelector: @@ -87,6 +90,7 @@ export const CONTEXTUAL_TOURS = [ advanceOnFeatureInteraction: 'terminal-pane-split' }, { + id: 'agent-sessions-parallel', title: 'Start another task in parallel', body: 'Each worktree gets its own branch, so parallel work stays separate.', targetSelector: '[data-contextual-tour-target="workspace-create-control"]', @@ -100,6 +104,7 @@ export const CONTEXTUAL_TOURS = [ id: 'browser', steps: [ { + id: 'browser-grab', title: 'Grab page context for agents', body: "Use the grab tool to copy a page element's context for agents.", targetSelector: '[data-contextual-tour-target="browser-grab-control"]', @@ -107,12 +112,14 @@ export const CONTEXTUAL_TOURS = [ preferredPlacement: 'bottom' }, { + id: 'browser-annotate', title: 'Mark design feedback in place', body: 'Annotate elements and send those notes to an agent.', targetSelector: '[data-contextual-tour-target="browser-annotation-control"]', preferredPlacement: 'bottom' }, { + id: 'browser-logins', title: 'Stay logged in', body: 'Bring your existing logins into Orca to stay signed in immediately.', // Prefer visible Import; use overflow after the hint is dismissed. @@ -141,17 +148,20 @@ export const CONTEXTUAL_TOURS = [ id: 'tasks', steps: [ { + id: 'tasks-source', title: 'Choose the work source', body: 'Switch between connected providers and project filters without changing pages.', targetSelector: '[data-contextual-tour-target="tasks-source-filters"]', requiredForStart: true }, { + id: 'tasks-filter', title: 'Filter to the work you need', body: 'Use presets and search to narrow issues, reviews, merge requests, or tasks.', targetSelector: '[data-contextual-tour-target="tasks-search-presets"]' }, { + id: 'tasks-start', title: 'Start from work items', body: 'Use Start or Open on a task, issue, review, or merge request to bring its context into a workspace.', targetSelector: @@ -181,6 +191,7 @@ export const CONTEXTUAL_TOURS = [ id: 'floating-workspace', steps: [ { + id: 'floating-workspace-repos', title: 'Run an agent across every repo', body: 'Agents here run in any folder you choose. Point one at the directory above your services to work across all your repos at once.', // Action anchors exist only in empty state; use the panel when floating tabs already exist. @@ -190,6 +201,7 @@ export const CONTEXTUAL_TOURS = [ preferredPlacement: 'left' }, { + id: 'floating-workspace-scratchpad', title: 'Or use it as a scratchpad', body: 'Open agents, scratch terminals, notes, and browser tabs without cluttering the worktree you’re focused on.', targetSelector: @@ -203,18 +215,21 @@ export const CONTEXTUAL_TOURS = [ allowedActiveModals: ['new-workspace-composer'], steps: [ { + id: 'workspace-creation-project', title: 'Pick a project', body: 'Orca isolates each task in its own worktree, branched off your base.', targetSelector: '[data-contextual-tour-target="workspace-creation-project"]', requiredForStart: true }, { + id: 'workspace-creation-name', title: 'Name it, or start from existing work', body: 'Start from a linked task for a short issue or PR name. Or leave it blank to auto-name it from your first agent message.', targetSelector: '[data-contextual-tour-target="workspace-creation-name"]', control: { kind: 'auto-rename-branch-from-work' } }, { + id: 'workspace-creation-agent', title: 'Choose what agent starts the work', body: 'Pick the agent that should be opened when this worktree is created.', targetSelector: '[data-contextual-tour-target="workspace-creation-agent"]' @@ -223,6 +238,8 @@ export const CONTEXTUAL_TOURS = [ } ] as const satisfies readonly ContextualTour[] +export type ContextualTourStepId = (typeof CONTEXTUAL_TOURS)[number]['steps'][number]['id'] + export const CONTEXTUAL_TOUR_IDS = CONTEXTUAL_TOURS.map((tour) => tour.id) export function isContextualTourId(value: unknown): value is ContextualTourId {