Skip to content
Closed
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
138 changes: 138 additions & 0 deletions apps/desktop/src/main/__tests__/korean-catalogs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { getArtifactCopy } from '../../renderer/locales/artifact-copy.js';
import { getBrowserCopy } from '../../renderer/locales/browser-copy.js';
import { getDesktopConversationCopy } from '../../renderer/locales/conversation-copy.js';
import { getExternalSessionImportCopy } from '../../renderer/locales/external-session-import-copy.js';
import { getMcpCopy } from '../../renderer/locales/mcp-copy.js';
import { getPlanModeCopy } from '../../renderer/locales/plan-mode-copy.js';
import { getBotSettingsCopy } from '../../renderer/locales/settings-bot-copy.js';
import { getDailyReviewSettingsCopy } from '../../renderer/locales/settings-daily-review-copy.js';
import { getDataSettingsCopy } from '../../renderer/locales/settings-data-copy.js';
import { getHealthCenterCopy } from '../../renderer/locales/settings-health-copy.js';
import type { HealthSignal } from '@maka/core/health';
import { getMemorySettingsCopy } from '../../renderer/locales/settings-memory-copy.js';
import { getSettingsNavigationCopy } from '../../renderer/locales/settings-navigation-copy.js';
import { getSettingsProjectsCopy } from '../../renderer/locales/settings-projects-copy.js';
import { getProviderSettingsCopy } from '../../renderer/features/connection-settings/index.js';
import { getSettingsSharedCopy } from '../../renderer/locales/settings-shared-copy.js';
import { getSubagentSettingsCopy } from '../../renderer/locales/settings-subagents-copy.js';
import { getSettingsTasksCopy } from '../../renderer/locales/settings-tasks-copy.js';
import { settingsTestResultMessage } from '../../renderer/locales/settings-test-result-copy.js';
import type { SettingsTestResult } from '@maka/core/settings';
import { getUsageSettingsCopy } from '../../renderer/locales/settings-usage-copy.js';
import { getWebSearchSettingsCopy } from '../../renderer/locales/settings-web-search-copy.js';
import { getSettingsPreferencesCopy } from '../../renderer/locales/settings-preferences-copy.js';

function collectStrings(value: unknown): string[] {
if (typeof value === 'string') return [value];
if (typeof value === 'function' || value === null || typeof value !== 'object') return [];
return Object.values(value).flatMap(collectStrings);
}

test('all Desktop slice-2 catalogs expose visibly Korean copy', () => {
const values = [
getArtifactCopy('ko').pane.copy,
getBrowserCopy('ko').title,
getDesktopConversationCopy('ko').actions.conversationErrorTitle,
getExternalSessionImportCopy('ko').sourceLabel,
getMcpCopy('ko').page.setupTitle,
getPlanModeCopy('ko').proposal.execute,
getBotSettingsCopy('ko').overview.reload,
getDailyReviewSettingsCopy('ko').aria,
getDataSettingsCopy('ko').backupTitle,
getHealthCenterCopy('ko').title,
getMemorySettingsCopy('ko').text.title,
getSettingsNavigationCopy('ko').sections.general.label,
getSettingsProjectsCopy('ko').runtimeHost.title,
getProviderSettingsCopy('ko').detail.modelKey,
getSettingsSharedCopy('ko').modalLabel,
getSubagentSettingsCopy('ko').section.title,
getSettingsTasksCopy('ko').listAria,
getUsageSettingsCopy('ko').details,
getWebSearchSettingsCopy('ko').search,
];

for (const value of values) assert.match(value, /[가-힣]/u);
const proxyDisabled: SettingsTestResult = { ok: false, code: 'proxy_disabled', message: '' };
assert.match(settingsTestResultMessage(proxyDisabled, 'ko'), /[가-힣]/u);
});

test('Korean catalog additions do not leak Korean into English or leave key dynamic copy in English', () => {
const catalogs = [
getArtifactCopy,
getBrowserCopy,
getDesktopConversationCopy,
getExternalSessionImportCopy,
getMcpCopy,
getPlanModeCopy,
getBotSettingsCopy,
getDailyReviewSettingsCopy,
getDataSettingsCopy,
getHealthCenterCopy,
getMemorySettingsCopy,
getSettingsNavigationCopy,
getSettingsProjectsCopy,
getProviderSettingsCopy,
getSettingsSharedCopy,
getSubagentSettingsCopy,
getSettingsTasksCopy,
getUsageSettingsCopy,
getWebSearchSettingsCopy,
];
for (const getCopy of catalogs) {
assert.equal(collectStrings(getCopy('en')).some((value) => /[가-힣]/u.test(value)), false);
}

assert.match(getMemorySettingsCopy('ko').text.localFile, /[가-힣]/u);
assert.match(getProviderSettingsCopy('ko').panel.loadFailed, /[가-힣]/u);
assert.match(getProviderSettingsCopy('ko').oauthFlow.refreshFailed, /[가-힣]/u);
assert.match(getHealthCenterCopy('ko').signalMessage({
id: 'connection:test:runtime',
label: '테스트',
scope: 'llm_connection',
layer: 'runtime_probe',
status: 'ok',
source: 'runtime_probe',
checkedAt: 0,
message: '',
} satisfies HealthSignal), /[가-힣]/u);
const inspector = getDesktopConversationCopy('ko').inspector;
assert.equal(inspector.callKind('daily_review'), '일일 검토');
assert.equal(inspector.permissionDecision('allow'), '허용됨');
assert.equal(inspector.retries(2), '2회 재시도');
assert.equal(getMcpCopy('ko').toast.importedDetail(2), '2 서버를 가져왔습니다.');
assert.equal(getMcpCopy('ko').row.tools(2), '2개 도구');
assert.equal(getArtifactCopy('ko').preview.externalLinks(2), '이 미리보기에서는 외부 링크가 비활성화되었습니다. · 외부 링크 2개');
assert.equal(getUsageSettingsCopy('ko').recordCount(2), '2개 기록');
assert.deepEqual(getSettingsPreferencesCopy('en').personalization.localeOptions.at(-1), ['ko', '한국어']);
assert.deepEqual(getSettingsPreferencesCopy('zh').personalization.localeOptions.at(-1), ['ko', '한국어']);
});

test('Korean representative surfaces preserve interpolation', () => {
const plan = getPlanModeCopy('ko');
const conversation = getDesktopConversationCopy('ko');

assert.equal(plan.abandonConfirmation.description('Release plan'), '“Release plan”의 실행 기록은 유지되지만 다시 시작할 수 없습니다.');
assert.equal(plan.execution.stepCount(2, 3), '2/3단계');
assert.equal(conversation.actions.branchCreatedDescription('Release branch'), '새 작업: Release branch');
});
5 changes: 5 additions & 0 deletions apps/desktop/src/main/computer-use/status-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ const COPY: UiCatalog<StatusItemCopy> = {
stopUnnamed: 'Stop Computer Use',
empty: 'No Active Sessions',
},
ko: {
stopUsing: (appName) => `${appName} 사용 중지`,
stopUnnamed: 'Computer Use 중지',
empty: '활성 세션 없음',
},
};

function defaultResolveLocale(): UiLocale {
Expand Down
46 changes: 46 additions & 0 deletions apps/desktop/src/main/native-diagnostic-dialog-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,52 @@ const COPY = {
exit: '退出',
},
},
ko: {
dialog: {
copy: '진단 정보 복사',
copyAgain: '다시 복사',
copied: '진단 정보가 복사되었습니다. 문제 보고서에 붙여넣을 수 있습니다.',
copyFailed: '진단 정보를 복사할 수 없습니다.',
},
fatalStartup: {
title: 'Maka를 시작하지 못했습니다',
message: 'Maka가 시작을 완료하지 못했습니다.',
detail: '시작 중 예기치 않은 오류가 발생했습니다. 진단 정보를 복사하면 자세한 내용을 확인할 수 있습니다.',
exit: '종료',
},
rendererGone: {
title: 'Maka 복구 필요',
message: 'Maka 인터페이스가 예기치 않게 중지되었습니다.',
detail: '인터페이스만 복구하며 Maka는 다시 시작하지 않습니다. Runtime Host와 실행 중인 작업은 유지됩니다.',
recover: '인터페이스 복구',
exit: '종료',
},
runtimeHostRecovery: {
title: 'Maka Runtime Host 수정 필요',
message: '이 작업 공간을 관리하는 Runtime Host를 시작할 수 없습니다.',
detail: 'Maka는 이 Desktop에서 선택한 Runtime Host를 수정할 수 있습니다. 작업 공간, Host ID, 자격 증명 및 설정은 유지됩니다.',
activeTasks: 'Host에서 실행 중인 작업이 있을 수 있습니다. 계속하면 먼저 중단한 후 Host를 다시 시작합니다.',
repairFailed: '이전 수정이 완료되지 않았습니다. 진단 정보를 복사하면 자세한 내용을 확인할 수 있습니다.',
repair: 'Runtime Host 수정',
repairAndRestart: '수정 후 Host 다시 시작',
exit: '종료',
},
defaultRuntimeHostRecovery: {
title: '기본 Runtime Host에 연결할 수 없습니다',
connectFailed: (profileName) => `${profileName}에 연결할 수 없습니다`,
detail: '다시 시도하거나 Local을 기본 Host로 사용하거나 현재 선택을 유지하고 나중에 설정에서 처리할 수 있습니다.',
retry: '다시 시도',
useLocal: 'Local 사용',
keepOffline: '오프라인 유지',
},
storageRootRepair: {
title: 'Maka 작업 공간 수정 필요',
message: 'Maka가 이 작업 공간을 확인할 수 없습니다.',
detail: (workspaceRoot) => `시스템의 디스크 ID가 변경되었을 수 있습니다. 복사된 작업 공간이 아닌 이 컴퓨터의 원래 Maka 작업 공간인 경우에만 수정하세요.\n\n${workspaceRoot}`,
repair: '작업 공간 수정',
exit: '종료',
},
},
} satisfies UiCatalog<NativeDiagnosticDialogCopy>;

export function getNativeDiagnosticDialogCopy(locale: UiLocale): NativeDiagnosticDialogCopy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,25 @@ const COPY: Catalog = {
noBundle: 'Not running from a .app bundle, so there is nothing to drag. Add it manually in System Settings.',
},
},
ko: {
accessibility: {
headline: (appName) => `${appName}을(를) 위 목록으로 드래그하여 손쉬운 사용을 허용하세요`,
fallback: '또는 시스템 설정에서 +를 클릭하고 응용 프로그램에서 앱을 선택하세요.',
granted: '손쉬운 사용 활성화됨',
dismiss: '닫기',
dragHint: '여기로 드래그',
noBundle: '.app 번들에서 실행되지 않아 드래그할 수 없습니다. 시스템 설정에서 수동으로 추가하세요.',
},
screen_recording: {
headline: (appName) => `${appName}을(를) 위 목록으로 드래그하여 화면 기록을 허용하세요`,
fallback: '또는 시스템 설정에서 +를 클릭하고 응용 프로그램에서 앱을 선택하세요.',
granted: '화면 기록 활성화됨',
dismiss: '닫기',
dragHint: '여기로 드래그',
restartHint: '여전히 거부됨으로 표시되면 앱을 다시 시작하세요. macOS가 이전 거부 결과를 캐시합니다.',
noBundle: '.app 번들에서 실행되지 않아 드래그할 수 없습니다. 시스템 설정에서 수동으로 추가하세요.',
},
},
};

export function getPermissionOverlayCopy(
Expand Down
9 changes: 9 additions & 0 deletions apps/desktop/src/main/runtime-host-quit-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,13 @@ const COPY = {
cause: '原因',
button: '好',
},
ko: {
title: 'Maka를 안전하게 종료할 수 없습니다',
message: '로컬 Runtime Host를 안전하게 중지하지 못했습니다. Maka는 계속 실행 중입니다.',
detail: '종료가 취소되었습니다. 다시 시도하거나 문제가 계속되면 진단 정보를 확인하세요.',
process: (pid: number) => `Runtime Host 프로세스 PID: ${pid}`,
manual: '다시 시도해도 실패하면 보존해야 할 실행이 없는지 확인한 후 운영 체제의 프로세스 관리 도구로 이 PID를 중지하세요.',
cause: '원인',
button: '확인',
},
} as const;
22 changes: 22 additions & 0 deletions apps/desktop/src/main/runtime-host-upgrade-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,26 @@ const UPGRADE_COPY = {
resource: 'Runtime 资源', graph: 'Agent Graph', other: '其他后台活动',
},
},
ko: {
title: '이전 Runtime Host가 실행 중입니다',
message: '다른 Runtime Host 프로세스가 이 작업 공간을 사용하고 있습니다.',
restart: 'Runtime Host 다시 시작',
replace: 'Host 중지 후 계속',
wait: '대기',
cancel: '시작 취소',
uptime: (n: number) => `약 ${n}분 동안 실행됨`,
connections: (n: number) => `${n}개의 다른 클라이언트가 아직 연결되어 있습니다`,
operations: (n: number) => `${n}개의 작업이 실행 중입니다`,
idleNotVerified: '안전한 교체 확인 중 Host가 유휴 상태인지 확인할 수 없습니다.',
unknownActivity: '이 Host 버전은 백그라운드 활동을 보고할 수 없습니다.',
processId: (pid: number) => `프로세스 ID(PID): ${pid}`,
restartWarning: '다시 시작하면 영구 상태는 유지되지만 진행 중인 외부 작업이 중단될 수 있습니다.',
replaceWarning: '중지하면 영구 상태는 유지되지만 진행 중인 외부 작업이 중단될 수 있습니다.',
replaceExplanation: 'Maka가 이 Host를 중지하고 안전하게 교체한 후 시작을 계속합니다.',
exitOwner: (pid: number) => `이 Host를 안전하게 교체하려면 시스템 프로세스 관리자에서 프로세스 ${pid}를 종료하세요.`,
waitExplanation: '기다리면 이 Host가 종료된 후 Maka가 자동으로 계속합니다.',
activity: {
goal: '목표', scheduledTask: '예약된 작업', dailyReview: '일일 검토', execution: '활성 실행', resource: '런타임 리소스', graph: '에이전트 그래프', other: '기타 백그라운드 활동',
},
},
} as const;
Loading