Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions config/scripts/locale-ko-key-overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -5089,5 +5089,98 @@
},
"auto.components.right.sidebar.AiVaultPanel.noAgentsSelected": {
"ko": "선택된 에이전트가 없습니다"
},
"auto.components.contextual.tours.contextual.tour.step.copy.agent.sessions.parallel.body": {
"ko": "워크트리마다 자체 브랜치를 가지므로 병렬로 진행하는 작업이 서로 섞이지 않습니다."
},
"auto.components.contextual.tours.contextual.tour.step.copy.agent.sessions.parallel.title": {
"ko": "다른 작업을 병렬로 시작하기"
},
"auto.components.contextual.tours.contextual.tour.step.copy.agent.sessions.split.action": {
"ko": "Terminal 분할"
},
"auto.components.contextual.tours.contextual.tour.step.copy.agent.sessions.split.body": {
"ko": "{terminal.splitRight} 키로 두 번째 Terminal 창을 열거나, 창을 우클릭해 분할 옵션을 확인하세요."
},
"auto.components.contextual.tours.contextual.tour.step.copy.agent.sessions.split.title": {
"ko": "Terminal 창 분할"
},
"auto.components.contextual.tours.contextual.tour.step.copy.browser.annotate.body": {
"ko": "요소에 주석을 달고 그 메모를 agent에 보내세요."
},
"auto.components.contextual.tours.contextual.tour.step.copy.browser.annotate.title": {
"ko": "디자인 피드백을 화면에 바로 남기기"
},
"auto.components.contextual.tours.contextual.tour.step.copy.browser.grab.body": {
"ko": "grab 도구로 페이지 요소의 컨텍스트를 복사해 agent에 전달하세요."
},
"auto.components.contextual.tours.contextual.tour.step.copy.browser.grab.title": {
"ko": "agent를 위한 페이지 컨텍스트 가져오기"
},
"auto.components.contextual.tours.contextual.tour.step.copy.browser.logins.body": {
"ko": "기존 로그인 정보를 Orca로 가져와 바로 로그인 상태를 유지하세요."
},
"auto.components.contextual.tours.contextual.tour.step.copy.browser.logins.title": {
"ko": "로그인 상태 유지"
},
"auto.components.contextual.tours.contextual.tour.step.copy.floating.workspace.repos.body": {
"ko": "이곳의 agent는 선택한 어떤 폴더에서도 실행됩니다. 서비스들의 상위 디렉터리를 지정하면 모든 repo에서 한 번에 작업할 수 있습니다."
},
"auto.components.contextual.tours.contextual.tour.step.copy.floating.workspace.repos.title": {
"ko": "agent 하나로 모든 repo 다루기"
},
"auto.components.contextual.tours.contextual.tour.step.copy.floating.workspace.scratchpad.body": {
"ko": "집중하고 있는 워크트리를 어지럽히지 않고 agent, 임시 Terminal, 노트, 브라우저 탭을 열 수 있습니다."
},
"auto.components.contextual.tours.contextual.tour.step.copy.floating.workspace.scratchpad.title": {
"ko": "메모장처럼 사용하기"
},
"auto.components.contextual.tours.contextual.tour.step.copy.tasks.filter.body": {
"ko": "프리셋과 검색으로 이슈, 리뷰, merge requests, 작업의 범위를 좁히세요."
},
"auto.components.contextual.tours.contextual.tour.step.copy.tasks.filter.title": {
"ko": "필요한 작업만 필터링하기"
},
"auto.components.contextual.tours.contextual.tour.step.copy.tasks.source.body": {
"ko": "페이지를 이동하지 않고 연결된 제공자와 프로젝트 필터를 전환하세요."
},
"auto.components.contextual.tours.contextual.tour.step.copy.tasks.source.title": {
"ko": "작업 소스 선택"
},
"auto.components.contextual.tours.contextual.tour.step.copy.tasks.start.body": {
"ko": "작업, 이슈, 리뷰, merge request에서 시작 또는 열기를 눌러 해당 컨텍스트를 워크스페이스로 가져오세요."
},
"auto.components.contextual.tours.contextual.tour.step.copy.tasks.start.title": {
"ko": "작업 항목에서 시작하기"
},
"auto.components.contextual.tours.contextual.tour.step.copy.workspace.board.lanes.body": {
"ko": "상태가 바뀌면 워크스페이스를 레인 사이로 끌어다 놓으세요."
},
"auto.components.contextual.tours.contextual.tour.step.copy.workspace.board.lanes.title": {
"ko": "레인을 따라 작업 옮기기"
},
"auto.components.contextual.tours.contextual.tour.step.copy.workspace.board.plan.body": {
"ko": "프로젝트별이 아니라 상태별로 워크스페이스를 보고 싶을 때 보드를 사용하세요."
},
"auto.components.contextual.tours.contextual.tour.step.copy.workspace.board.plan.title": {
"ko": "보드에서 작업 계획하기"
},
"auto.components.contextual.tours.contextual.tour.step.copy.workspace.creation.agent.body": {
"ko": "이 워크트리가 생성될 때 열릴 agent를 선택하세요."
},
"auto.components.contextual.tours.contextual.tour.step.copy.workspace.creation.agent.title": {
"ko": "작업을 시작할 agent 선택"
},
"auto.components.contextual.tours.contextual.tour.step.copy.workspace.creation.name.body": {
"ko": "연결된 작업에서 시작하면 짧은 이슈나 PR 이름을 사용합니다. 비워 두면 첫 agent 메시지에서 이름이 자동으로 지정됩니다."
},
"auto.components.contextual.tours.contextual.tour.step.copy.workspace.creation.name.title": {
"ko": "이름을 정하거나 기존 작업에서 시작하기"
},
"auto.components.contextual.tours.contextual.tour.step.copy.workspace.creation.project.body": {
"ko": "Orca는 각 작업을 기준 브랜치에서 분기한 자체 워크트리로 분리합니다."
},
"auto.components.contextual.tours.contextual.tour.step.copy.workspace.creation.project.title": {
"ko": "프로젝트 선택"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,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"]'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,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 =
Expand All @@ -28,36 +29,6 @@ export type ContextualTourOverlayMeasurementResult =
telemetryTotalSteps: number
}

// Why: keyed by the step's stable id, not its position — inserting a step must
// not shift localized copy onto a neighbour. Thunks keep translate() out of
// module scope so the lookup resolves in the language active at render time.
const LOCALIZED_STEP_COPY: Record<string, { title: () => 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.'
)
},
'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[]
Expand Down Expand Up @@ -119,13 +90,10 @@ 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 localizedCopy = getLocalizedContextualTourStepCopy(activeStep?.id)
const localizedTitle = localizedCopy?.title ?? activeStep?.title
const localizedBody =
localizedCopy?.body ?? (activeStep ? getContextualTourStepCopy(activeStep) : undefined)
const progress = getContextualTourDisplayProgress({
tour: args.tour,
visibleStepIndexes,
Expand Down Expand Up @@ -162,7 +130,9 @@ export function measureContextualTourOverlayRenderState(args: {
'Next'
)
} as const)
: activeStep.primaryAction
: activeStep.primaryAction && localizedCopy?.primaryActionLabel
? { ...activeStep.primaryAction, label: localizedCopy.primaryActionLabel }
: activeStep.primaryAction
const secondaryAction = sidebarAlreadyVisible ? undefined : activeStep.secondaryAction

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// @vitest-environment happy-dom

import { afterEach, describe, expect, it } from 'vitest'
import { CONTEXTUAL_TOURS, type ContextualTour } from '../../../../shared/contextual-tours'
import { setRendererUiLanguage } from '@/i18n/i18n'
import { getLocalizedContextualTourStepCopy } from './contextual-tour-step-copy'

afterEach(async () => {
await setRendererUiLanguage('en')
})

describe('contextual tour step copy', () => {
it('translates every tour step, so no step falls back to English mid-tour', async () => {
await setRendererUiLanguage('ko')

for (const tour of CONTEXTUAL_TOURS as readonly ContextualTour[]) {
for (const step of tour.steps) {
const copy = getLocalizedContextualTourStepCopy(step.id)
expect(copy, `${tour.id} step "${step.title}" has no localized copy`).toBeDefined()
expect(copy?.title).not.toBe(step.title)
expect(copy?.body).not.toBe(step.body)
if (step.primaryAction) {
expect(copy?.primaryActionLabel).toBeDefined()
expect(copy?.primaryActionLabel).not.toBe(step.primaryAction.label)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
})

it('renders the floating workspace copy in Korean', async () => {
await setRendererUiLanguage('ko')

expect(getLocalizedContextualTourStepCopy('floating-workspace-scratchpad')?.title).toBe(
'메모장처럼 사용하기'
)
})

it('falls back to English copy for the active locale', () => {
expect(getLocalizedContextualTourStepCopy('floating-workspace-repos')?.title).toBe(
'Run an agent across every repo'
)
})
})
Loading