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
65 changes: 64 additions & 1 deletion config/scripts/locale-ko-key-overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,9 @@
"auto.components.TaskPage.fe28c9821f": {
"ko": "보기"
},
"auto.components.TaskPage.linearEmptyUnfilteredScope": {
"ko": "이 워크스페이스 범위에 이슈가 없습니다. 검색하거나 팀을 조정해 보세요."
},
"auto.components.Terminal.5b2c1a9e44": {
"ko": "agent CLI를 찾지 못했습니다. 하나를 설치하거나 설정에서 기본 agent를 선택하세요."
},
Expand Down Expand Up @@ -1151,6 +1154,9 @@
"auto.components.feature.wall.review.notes.diff.rows.f621c734f8": {
"ko": "메모 · 줄"
},
"auto.components.floating.terminal.FloatingTerminalToggleButton.4cb418b991": {
"ko": "플로팅 워크스페이스 표시, 새 활동"
},
"auto.components.github.CloseReasonDropdown.e1f2a3b4c5": {
"ko": "닫기 사유 선택"
},
Expand Down Expand Up @@ -1373,6 +1379,15 @@
"auto.components.jira.connect.dialog.fdd26d81cc": {
"ko": "Atlassian 계정 설정"
},
"auto.components.linear-issue-attribute-filter-dropdowns.allWorkspacesBody": {
"ko": "상태, 담당자, 레이블 필터는 단일 Linear 워크스페이스의 ID를 사용합니다. 해당 속성으로 필터링할 워크스페이스 하나를 선택하세요."
},
"auto.components.linear-issue-attribute-filter-dropdowns.allWorkspacesTitle": {
"ko": "워크스페이스 선택"
},
"auto.components.linear-issue-attribute-filter-dropdowns.teamRequired": {
"ko": "이 워크스페이스의 상태, 담당자, 레이블을 로드하려면 팀을 선택하세요."
},
"auto.components.linear.api.key.dialog.57a66522c8": {
"ko": "연결 중"
},
Expand Down Expand Up @@ -1955,6 +1970,12 @@
"auto.components.right.sidebar.right.panel.comment.composer.542bf6a7e2": {
"ko": "기울임"
},
"auto.components.right.sidebar.source.control.ai.push.failure.launch.216f762bd7": {
"ko": "워크스페이스 연결을 확인할 수 없습니다."
},
"auto.components.right.sidebar.source.control.ai.recovery.launch.216f762bd7": {
"ko": "워크스페이스 연결을 확인할 수 없습니다."
},
"auto.components.right.sidebar.source.control.discard.confirmation.2ae5a785b3": {
"ko": "스테이징되지 않은 변경 사항을 모두 삭제하시겠습니까?"
},
Expand Down Expand Up @@ -2321,6 +2342,12 @@
"auto.components.settings.IntegrationsPane.e74de656ce": {
"ko": "glab auth login"
},
"auto.components.settings.LinearAgentSkillGuide.noteLinkedBody": {
"ko": "작업에서 만든 워크트리는 이슈가 컨텍스트로 연결되어 있어 이슈 작업이 가장 원활합니다."
},
"auto.components.settings.LinearAgentSkillPane.howToUseDescription": {
"ko": "카드를 클릭해 프롬프트를 복사하세요. 스킬을 설치한 후 Linear에 연결된 워크트리에서 사용하세요."
},
"auto.components.settings.ManageSessionsSection.2896a50f50": {
"ko": "terminal {{value0}}(으)로 이동"
},
Expand Down Expand Up @@ -4082,6 +4109,9 @@
"auto.components.setup.guide.SetupGuideProgressRing.dac3a4724a": {
"ko": "설정 단계 {{value1}}개 중 {{value0}}개 완료"
},
"auto.components.shared.useDaemonActions.a702d4196e": {
"ko": "이 작업은 모든 워크스페이스의 모든 터미널 탭을 닫고 현재 터미널 세션의 종료를 요청합니다. 저장되지 않은 터미널 작업은 손실됩니다. 데몬 자체는 계속 실행되며 새 터미널은 즉시 열 수 있습니다. 이 작업은 되돌릴 수 없습니다."
},
"auto.components.shared.useDaemonActions.d18f3005c2": {
"ko": "세션 {{value0}}개{{value1}}이(가) 종료를 거부했습니다."
},
Expand Down Expand Up @@ -4151,6 +4181,9 @@
"auto.components.sidebar.DeleteWorktreeWarningPanels.c4f96a6e18": {
"ko": "메인 워크트리"
},
"auto.components.sidebar.ForgetSshWorkspaceDialog.disconnectedBody": {
"ko": "이 워크스페이스의 SSH 호스트가 연결되어 있지 않습니다. 재연결하여 원격에서도 삭제하거나 Orca에서만 제거하세요."
},
"auto.components.sidebar.HostRemoveDialog.1a2b3c4d5e": {
"ko": "{{value0}} 제거됨"
},
Expand All @@ -4172,6 +4205,15 @@
"auto.components.sidebar.HostRemoveDialog.8b9c0d1e2f": {
"ko": "호스트 제거"
},
"auto.components.sidebar.HostRemoveDialog.manyWorkspaces": {
"ko": "워크스페이스 {{count}}개"
},
"auto.components.sidebar.HostRemoveDialog.oneWorkspace": {
"ko": "워크스페이스 1개"
},
"auto.components.sidebar.HostRemoveDialog.workspacesFailed": {
"ko": "이 호스트에서 워크스페이스 {{count}}개를 제거할 수 없었습니다. 재시도할 수 있도록 호스트가 유지되었습니다."
},
"auto.components.sidebar.HostRenameDialog.1a2b3c4d5e": {
"ko": "호스트 이름 바꾸기"
},
Expand Down Expand Up @@ -4341,7 +4383,7 @@
"ko": "고정"
},
"auto.components.sidebar.WorktreeContextMenu.8d9cd19d09": {
"ko": "상위 워크스페이스 열기"
"ko": "상위 워크트리 열기"
},
"auto.components.sidebar.WorktreeContextMenu.8dacff1fe0": {
"ko": "읽음으로 표시"
Expand All @@ -4361,6 +4403,9 @@
"auto.components.sidebar.WorktreeList.f94466bc39": {
"ko": "그룹 삭제 후에도 {{value0}}/{{value1}} 프로젝트{{value2}}가 남아 있습니다."
},
"auto.components.sidebar.WorktreeList.failedUnnestWorkspace": {
"ko": "워크스페이스 펼치기 실패"
},
"auto.components.sidebar.WorktreeList.groupDeleteFailed": {
"ko": "그룹을 삭제하지 못했습니다."
},
Expand Down Expand Up @@ -4394,6 +4439,12 @@
"auto.components.sidebar.add.repo.local.start.actions.sshCreateUnavailable": {
"ko": "아직 SSH 호스트에서는 사용할 수 없습니다."
},
"auto.components.sidebar.delete.worktree.toast.locked": {
"ko": "이 워크스페이스는 Git에 의해 잠겨 있습니다. 저장소에서 git worktree unlock <worktree-path>을 실행한 후 삭제를 다시 시도하세요."
},
"auto.components.sidebar.delete.worktree.toast.lockedReason": {
"ko": "이 워크스페이스는 Git에 의해 잠겨 있습니다. Git 보고: {{value0}}. 저장소에서 git worktree unlock <worktree-path>을 실행한 후 삭제를 다시 시도하세요."
},
"auto.components.sidebar.index.b826a98b6f": {
"ko": "사용 중"
},
Expand Down Expand Up @@ -4805,6 +4856,9 @@
"auto.components.tab.group.AiVaultSessionDropLayer.openLocalWorkspace": {
"ko": "세션을 재개하기 전에 로컬 워크스페이스를 여세요."
},
"auto.components.tab.group.AiVaultSessionDropLayer.openSupportedWorkspace": {
"ko": "세션을 재개하기 전에 워크스페이스를 여세요."
},
"auto.components.tab.group.AiVaultSessionDropLayer.sessionQueued": {
"ko": "세션이 대기열에 추가됨"
},
Expand Down Expand Up @@ -4901,6 +4955,12 @@
"auto.components.terminal.pane.TerminalSessionStateSaveFailureDialog.6bee0c8f17": {
"ko": "디스크 공간 분석기 열기"
},
"auto.components.terminal.pane.TerminalSshReconnectOverlay.removeWorkspaceButton": {
"ko": "워크스페이스 제거"
},
"auto.components.terminal.pane.TerminalSshReconnectOverlay.removedBody": {
"ko": "이 워크스페이스의 SSH 호스트가 제거되어 더 이상 연결할 수 없습니다. 워크스페이스를 제거하여 정리하세요 — 원격 파일은 그대로 남아 있습니다."
},
"auto.components.terminal.quick.commands.TerminalQuickCommandAppendEnterSwitch.5fa607d807": {
"ko": "Enter 추가"
},
Expand Down Expand Up @@ -4973,6 +5033,9 @@
"auto.lib.projectSkillRuntime.wslUnavailable": {
"ko": "이 스킬을 설치하려면 프로젝트 런타임에 WSL이 필요합니다."
},
"auto.lib.sidebarWorktreeActivation.wakeEphemeralVmFailed": {
"ko": "임시 VM 워크스페이스 활성화 실패"
},
"auto.lib.source.control.agent.action.plan.3f0ea9aa0d": {
"ko": "agent 시작 명령을 만들 수 없습니다."
},
Expand Down
221 changes: 221 additions & 0 deletions config/scripts/locale-ko-key-overrides.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,156 @@ import fs from 'node:fs'

import { describe, expect, it } from 'vitest'

import {
repairTranslatedValue,
repairCatalog,
setLeaf,
collectStringLeaves
} from './locale-translation-policy.mjs'

const enCatalog = JSON.parse(
fs.readFileSync(new URL('../../src/renderer/src/i18n/locales/en.json', import.meta.url), 'utf8')
)
const koCatalog = JSON.parse(
fs.readFileSync(new URL('../../src/renderer/src/i18n/locales/ko.json', import.meta.url), 'utf8')
)
const enValues = new Map(collectStringLeaves(enCatalog).map(({ key, value }) => [key, value]))

const workspaceTermCorrections = [
{
key: 'auto.components.TaskPage.linearEmptyUnfilteredScope',
before: '이 워크트리 범위에 이슈가 없습니다. 검색하거나 팀을 조정해 보세요.',
expected: '이 워크스페이스 범위에 이슈가 없습니다. 검색하거나 팀을 조정해 보세요.'
},
{
key: 'auto.components.floating.terminal.FloatingTerminalToggleButton.4cb418b991',
before: '플로팅 워크트리 표시, 새 활동',
expected: '플로팅 워크스페이스 표시, 새 활동'
},
{
key: 'auto.components.linear-issue-attribute-filter-dropdowns.allWorkspacesBody',
before:
'상태, 담당자, 레이블 필터는 단일 Linear �크트리의 ID를 사용합니다. 필터링할 워크트리를 선택하세요.',
expected:
'상태, 담당자, 레이블 필터는 단일 Linear 워크스페이스의 ID를 사용합니다. 해당 속성으로 필터링할 워크스페이스 하나를 선택하세요.'
},
{
key: 'auto.components.linear-issue-attribute-filter-dropdowns.allWorkspacesTitle',
before: '워크트리 선택',
expected: '워크스페이스 선택'
},
{
key: 'auto.components.linear-issue-attribute-filter-dropdowns.teamRequired',
before: '이 워크트리의 상태, 담당자, 레이블을 로드하려면 팀을 선택하세요.',
expected: '이 워크스페이스의 상태, 담당자, 레이블을 로드하려면 팀을 선택하세요.'
},
{
key: 'auto.components.right.sidebar.source.control.ai.push.failure.launch.216f762bd7',
before: '워크트리 연결을 확인할 수 없습니다.',
expected: '워크스페이스 연결을 확인할 수 없습니다.'
},
{
key: 'auto.components.right.sidebar.source.control.ai.recovery.launch.216f762bd7',
before: '워크트리 연결을 확인할 수 없습니다.',
expected: '워크스페이스 연결을 확인할 수 없습니다.'
},
{
key: 'auto.components.settings.LinearAgentSkillGuide.noteLinkedBody',
before:
'작업에서 만든 워크스페이스는 이슈가 컨텍스트로 연결되어 있어 이슈 작업이 가장 원활합니다.',
expected:
'작업에서 만든 워크트리는 이슈가 컨텍스트로 연결되어 있어 이슈 작업이 가장 원활합니다.'
},
{
key: 'auto.components.settings.LinearAgentSkillPane.howToUseDescription',
before:
'카드를 클릭해 프롬프트를 복사하세요. 스킬을 설치한 후 Linear에 연결된 워크스페이스에서 사용하세요.',
expected:
'카드를 클릭해 프롬프트를 복사하세요. 스킬을 설치한 후 Linear에 연결된 워크트리에서 사용하세요.'
},
{
key: 'auto.components.shared.useDaemonActions.a702d4196e',
before:
'이 작업은 모든 워크트리의 모든 터미널 탭을 닫고 현재 터미널 세션의 종료를 요청합니다. 저장되지 않은 터미널 작업은 손실됩니다. 데몬 자체는 계속 실행되며 새 터미널은 즉시 열 수 있습니다. 이 작업은 되돌릴 수 없습니다.',
expected:
'이 작업은 모든 워크스페이스의 모든 터미널 탭을 닫고 현재 터미널 세션의 종료를 요청합니다. 저장되지 않은 터미널 작업은 손실됩니다. 데몬 자체는 계속 실행되며 새 터미널은 즉시 열 수 있습니다. 이 작업은 되돌릴 수 없습니다.'
},
{
key: 'auto.components.sidebar.ForgetSshWorkspaceDialog.disconnectedBody',
before:
'이 워크트리의 SSH 호스트가 연결되어 있지 않습니다. 재연결하여 원격에서도 삭제하거나 Orca에서만 제거하세요.',
expected:
'이 워크스페이스의 SSH 호스트가 연결되어 있지 않습니다. 재연결하여 원격에서도 삭제하거나 Orca에서만 제거하세요.'
},
{
key: 'auto.components.sidebar.HostRemoveDialog.manyWorkspaces',
before: '워크트리 {{count}}개',
expected: '워크스페이스 {{count}}개'
},
{
key: 'auto.components.sidebar.HostRemoveDialog.oneWorkspace',
before: '워크트리 1개',
expected: '워크스페이스 1개'
},
{
key: 'auto.components.sidebar.HostRemoveDialog.workspacesFailed',
before:
'이 호스트에서 워크트리 {{count}}개를 제거할 수 없었습니다. 재시도할 수 있도록 호스트가 유지되었습니다.',
expected:
'이 호스트에서 워크스페이스 {{count}}개를 제거할 수 없었습니다. 재시도할 수 있도록 호스트가 유지되었습니다.'
},
{
key: 'auto.components.sidebar.WorktreeContextMenu.8d9cd19d09',
before: '상위 워크스페이스 열기',
expected: '상위 워크트리 열기'
},
{
key: 'auto.components.sidebar.WorktreeList.failedUnnestWorkspace',
before: '워크트리 펼치기 실패',
expected: '워크스페이스 펼치기 실패'
},
{
key: 'auto.components.sidebar.delete.worktree.toast.locked',
before:
'이 워크트리는 Git에 의해 잠겨 있습니다. 저장소에서 git worktree unlock <worktree-path>을 실행한 후 삭제를 다시 시도하세요.',
expected:
'이 워크스페이스는 Git에 의해 잠겨 있습니다. 저장소에서 git worktree unlock <worktree-path>을 실행한 후 삭제를 다시 시도하세요.'
},
{
key: 'auto.components.sidebar.delete.worktree.toast.lockedReason',
before:
'이 워크트리는 Git에 의해 잠겨 있습니다. Git 보고: {{value0}}. 저장소에서 git worktree unlock <worktree-path>을 실행한 후 삭제를 다시 시도하세요.',
expected:
'이 워크스페이스는 Git에 의해 잠겨 있습니다. Git 보고: {{value0}}. 저장소에서 git worktree unlock <worktree-path>을 실행한 후 삭제를 다시 시도하세요.'
},
{
key: 'auto.components.tab.group.AiVaultSessionDropLayer.openSupportedWorkspace',
before: '세션을 재개하기 전에 워크트리를 여세요.',
expected: '세션을 재개하기 전에 워크스페이스를 여세요.'
},
{
key: 'auto.components.terminal.pane.TerminalSshReconnectOverlay.removeWorkspaceButton',
before: '워크트리 제거',
expected: '워크스페이스 제거'
},
{
key: 'auto.components.terminal.pane.TerminalSshReconnectOverlay.removedBody',
before:
'이 워크트리의 SSH 호스트가 제거되어 더 이상 연결할 수 없습니다. 워크트리를 제거하여 정리하세요 — 원격 파일은 그대로 남아 있습니다.',
expected:
'이 워크스페이스의 SSH 호스트가 제거되어 더 이상 연결할 수 없습니다. 워크스페이스를 제거하여 정리하세요 — 원격 파일은 그대로 남아 있습니다.'
},
{
key: 'auto.lib.sidebarWorktreeActivation.wakeEphemeralVmFailed',
before: '임시 VM 워크트리 활성화 실패',
expected: '임시 VM 워크스페이스 활성화 실패'
}
]

function placeholders(value) {
return (value.match(/{{[^{}]+}}/g) ?? []).sort()
}

describe('locale-ko-key-overrides', () => {
it('keeps Korean key override data scoped to Korean values', () => {
const overrides = JSON.parse(
Expand All @@ -11,4 +161,75 @@ describe('locale-ko-key-overrides', () => {
expect(Object.keys(value)).toEqual(['ko'])
}
})

it('repairs the 22 Korean workspace/worktree object mismatches', () => {
for (const { key, before, expected } of workspaceTermCorrections) {
const enValue = enValues.get(key)
expect(enValue, key).toBeTypeOf('string')
expect(repairTranslatedValue({ key, enValue, localeValue: before, locale: 'ko' }), key).toBe(
expected
)
}
})

it('keeps corrected Korean workspace/worktree values stable', () => {
for (const { key, expected } of workspaceTermCorrections) {
const enValue = enValues.get(key)
const first = repairTranslatedValue({ key, enValue, localeValue: expected, locale: 'ko' })
expect(first, key).toBe(expected)
expect(repairTranslatedValue({ key, enValue, localeValue: first, locale: 'ko' }), key).toBe(
expected
)
expect(repairTranslatedValue({ key, enValue, localeValue: enValue, locale: 'ko' }), key).toBe(
expected
)
}
})

it('preserves all 22 terms through catalog repair', () => {
const catalog = structuredClone(koCatalog)
for (const { key, before } of workspaceTermCorrections) {
setLeaf(catalog, key, before)
}
for (let pass = 0; pass < 2; pass += 1) {
repairCatalog(enCatalog, catalog, 'ko')
const repaired = new Map(collectStringLeaves(catalog).map(({ key, value }) => [key, value]))
for (const { key, expected } of workspaceTermCorrections) {
expect(repaired.get(key), key).toBe(expected)
}
}

const shippedCatalog = structuredClone(koCatalog)
repairCatalog(enCatalog, shippedCatalog, 'ko')
const shipped = new Map(
collectStringLeaves(shippedCatalog).map(({ key, value }) => [key, value])
)
for (const { key, expected } of workspaceTermCorrections) {
expect(shipped.get(key), key).toBe(expected)
}
})

it('preserves placeholders and Git unlock command literals', () => {
for (const { key, before, expected } of workspaceTermCorrections) {
const enValue = enValues.get(key)
const repaired = repairTranslatedValue({ key, enValue, localeValue: before, locale: 'ko' })
const tokens = placeholders(enValue)
for (const value of [before, expected, repaired]) {
expect(placeholders(value), key).toEqual(tokens)
}

if (key.startsWith('auto.components.sidebar.delete.worktree.toast.locked')) {
for (const value of [enValue, before, expected, repaired]) {
expect(value, key).toContain('git worktree unlock <worktree-path>')
if (key.endsWith('lockedReason')) {
expect(value, key).toContain('{{value0}}')
}
}
}
if (key === 'auto.components.linear-issue-attribute-filter-dropdowns.allWorkspacesBody') {
expect(expected, key).not.toContain('\uFFFD')
expect(repaired, key).not.toContain('\uFFFD')
}
}
})
})
Loading