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
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@
*/

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { getStartupRecoveryCopy } from '../runtime-host-boot-copy.js';
import test from 'node:test';
import { nativeFileDialogCopy } from '../native-file-dialog-copy.js';

test('startup recovery copy preserves Traditional Chinese', () => {
const copy = getStartupRecoveryCopy('zh-TW');
assert.equal(copy.storageRoot.title, 'Maka 工作區需要修復');
assert.match(copy.storageRoot.detail('C:\\Maka'), /磁碟識別碼/);
assert.equal(copy.runtimeHost.title, '預設 Runtime Host 無法連線');
assert.match(copy.runtimeHost.detail('失敗'), /設定中處理/);
test('every native file dialog string is translated in each locale', () => {
const en = nativeFileDialogCopy('en');
for (const locale of ['zh-CN', 'zh-TW'] as const) {
const copy = nativeFileDialogCopy(locale);
for (const key of Object.keys(en) as Array<keyof typeof en>) {
assert.notEqual(copy[key], en[key], `${locale}: ${key} is still the English string`);
assert.match(copy[key], /[一-鿿]/u, `${locale}: ${key} carries no Han text`);
}
}
});
2 changes: 2 additions & 0 deletions apps/desktop/src/main/__tests__/pet-pack-import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ test('IPC imports only the native picker result and returns stable envelopes', a
},
settingsStore: createSettingsStore(stateRoot),
now: () => 42,
resolveLocale: async () => 'en' as const,
});

const result = await handlers.get('pets:importLocalDirectory')?.({}, '/untrusted/path');
Expand Down Expand Up @@ -211,6 +212,7 @@ test('IPC returns sprite bytes, removes the pack, and emits only real mutations'
},
settingsStore,
now: () => 84,
resolveLocale: async () => 'en' as const,
});
await settingsStore.update({ personalization: { selectedPetId: 'likun.missing' } });
assert.equal(await handlers.get('pets:getSelection')?.({}), null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ test("projects an empty Skill surface until a remote Project is selected", async
getDefaultPermissionMode: async () => "ask",
openPath: async () => "",
allowLocalPaths: false,
resolveLocale: async () => 'en' as const,
});

for (const channel of [
Expand All @@ -68,6 +69,7 @@ test("binds new-session Skill discovery to its explicit Project", async () => {
const resolvedProjectIds: Array<string | null | undefined> = [];
let target: unknown;
registerRuntimeHostSkillsIpc({
resolveLocale: async () => 'en' as const,
ipcMain: {
handle: (channel, listener) => handlers.set(channel, listener),
handleReconnectableRead: (channel, listener) => handlers.set(channel, listener),
Expand Down
19 changes: 15 additions & 4 deletions apps/desktop/src/main/markdown-save-ipc-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,27 @@

import type { IpcMain } from 'electron';
import { saveMarkdownViaDialog } from './markdown-save-main.js';
import type { UiLocale } from '@maka/core/ui-locale';
import type { createMainWindowController } from './main-window.js';
import { nativeFileDialogCopy } from './native-file-dialog-copy.js';

export function registerMarkdownSaveIpc(input: {
readonly ipcMain: Pick<IpcMain, 'handle'>;
readonly mainWindowController: ReturnType<typeof createMainWindowController>;
readonly resolveLocale: () => Promise<UiLocale>;
}): void {
input.ipcMain.handle('daily-review:saveMarkdownToFile', (_event, value) =>
saveMarkdownViaDialog(input.mainWindowController, value, 'Save daily review'),
input.ipcMain.handle('daily-review:saveMarkdownToFile', async (_event, value) =>
saveMarkdownViaDialog(
input.mainWindowController,
value,
nativeFileDialogCopy(await input.resolveLocale()).saveDailyReview,
),
);
input.ipcMain.handle('chat:saveConversationToFile', (_event, value) =>
saveMarkdownViaDialog(input.mainWindowController, value, 'Save conversation'),
input.ipcMain.handle('chat:saveConversationToFile', async (_event, value) =>
saveMarkdownViaDialog(
input.mainWindowController,
value,
nativeFileDialogCopy(await input.resolveLocale()).saveConversation,
),
);
}
70 changes: 70 additions & 0 deletions apps/desktop/src/main/native-file-dialog-copy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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 type { UiCatalog, UiLocale } from '@maka/core/ui-locale';

/**
* Copy for the native open/save panels. Format names (`Markdown`,
* `PNG and JPEG`) stay untranslated: they identify a file format, not a
* product concept. `allFiles` is translated because the panel lists it beside
* the OS's own localized entries.
*/
interface NativeFileDialogCopy {
readonly referenceFolder: string;
readonly addAttachments: string;
readonly importSkillSource: string;
readonly importCustomPet: string;
readonly saveDailyReview: string;
readonly saveConversation: string;
readonly allFiles: string;
}

const COPY = {
'zh-CN': {
referenceFolder: '引用文件夹',
addAttachments: '添加附件',
importSkillSource: '导入 Skill 源文件',
importCustomPet: '导入自定义宠物',
saveDailyReview: '保存每日回顾',
saveConversation: '保存对话',
allFiles: '所有文件',
},
'zh-TW': {
referenceFolder: '引用資料夾',
addAttachments: '新增附件',
importSkillSource: '匯入 Skill 原始檔',
importCustomPet: '匯入自訂寵物',
saveDailyReview: '儲存每日回顧',
saveConversation: '儲存對話',
allFiles: '所有檔案',
},
en: {
referenceFolder: 'Reference folder',
addAttachments: 'Add attachments',
importSkillSource: 'Import Skill source',
importCustomPet: 'Import custom pet',
saveDailyReview: 'Save daily review',
saveConversation: 'Save conversation',
allFiles: 'All Files',
},
} satisfies UiCatalog<NativeFileDialogCopy>;

export function nativeFileDialogCopy(locale: UiLocale): NativeFileDialogCopy {
return COPY[locale];
}
5 changes: 4 additions & 1 deletion apps/desktop/src/main/pet-pack-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import {
} from '@maka/storage/pet-pack-store';
import type { SettingsStore } from '@maka/storage/settings-store';
import type { createMainWindowController } from './main-window.js';
import type { UiLocale } from '@maka/core/ui-locale';
import { nativeFileDialogCopy } from './native-file-dialog-copy.js';

type MainWindowController = Pick<
ReturnType<typeof createMainWindowController>,
Expand Down Expand Up @@ -123,6 +125,7 @@ export function registerPetPackIpc(input: {
readonly settingsStore: Pick<SettingsStore, 'get' | 'update'>;
readonly store?: PetPackStore;
readonly now?: () => number;
readonly resolveLocale: () => Promise<UiLocale>;
}): void {
const store = input.store ?? createPetPackStore(input.workspaceRoot);
const now = input.now ?? Date.now;
Expand Down Expand Up @@ -207,7 +210,7 @@ export function registerPetPackIpc(input: {
);
input.ipcMain.handle('pets:importLocalDirectory', async (): Promise<PetPackImportResult> => {
const selection = await input.mainWindowController.showOpenDialog({
title: 'Import custom pet',
title: nativeFileDialogCopy(await input.resolveLocale()).importCustomPet,
properties: ['openDirectory'],
});
if (selection.canceled || selection.filePaths.length === 0) {
Expand Down
90 changes: 0 additions & 90 deletions apps/desktop/src/main/runtime-host-boot-copy.ts

This file was deleted.

20 changes: 16 additions & 4 deletions apps/desktop/src/main/runtime-host-boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ import { computerUseServiceHealth } from "./computer-use-host.js";
import { registerDesktopDiagnosticsIpc } from "./desktop-diagnostics-ipc-main.js";
import { assembleDesktopNativeCapabilities } from "./desktop-native-capability-assembly.js";
import { clientSettingsConfirmation } from "./client-settings-confirmation-copy.js";
import { nativeFileDialogCopy } from "./native-file-dialog-copy.js";
import { createDesktopLocaleAuthority } from "./desktop-locale-authority.js";
import { buildRiveWorkflowTool } from "./rive-workflow-tool.js";
import { applyAppIcon } from "./app-icon-surface.js";
Expand Down Expand Up @@ -1092,7 +1093,13 @@ mcpManager.onChange(() => {
});

registerPersistentClientIpc();
registerPetPackIpc({ ipcMain, workspaceRoot, mainWindowController, settingsStore });
registerPetPackIpc({
ipcMain,
workspaceRoot,
mainWindowController,
settingsStore,
resolveLocale: () => desktopLocale.resolve(),
});
const browserIpc = registerBrowserIpc({
mainWindowController,
isHostActive: (scope) => runtimeHostManager?.ownsScope(scope) === true,
Expand Down Expand Up @@ -1701,6 +1708,7 @@ function registerHostClientIpc(
ipcMain: scopedIpc,
});
registerRuntimeHostSkillsIpc({
resolveLocale: () => desktopLocale.resolve(),
ipcMain: scopedIpc,
client,
workspaceRoot,
Expand Down Expand Up @@ -1879,7 +1887,11 @@ function registerPersistentClientIpc(): void {
await clientSettingsEffects.apply(settings, true);
},
});
registerMarkdownSaveIpc({ ipcMain, mainWindowController });
registerMarkdownSaveIpc({
ipcMain,
mainWindowController,
resolveLocale: () => desktopLocale.resolve(),
});
registerDesktopRuntimeHostProfileIpc(ipcMain, runtimeHostProfileService);
registerDesktopGuestSessionMountIpc(
ipcMain,
Expand Down Expand Up @@ -1987,15 +1999,15 @@ function registerPersistentClientIpc(): void {
if (!local || local.readiness !== 'ready') throw new Error('Local Runtime Host is unavailable');
const hostId = local.candidate.client.hostId;
const result = await mainWindowController.showOpenDialog({
title: 'Reference folder',
title: nativeFileDialogCopy(await desktopLocale.resolve()).referenceFolder,
properties: ['openDirectory'],
});
if (result.canceled || !result.filePaths[0]) return { ok: false, reason: 'cancelled' };
return { ok: true, reference: { hostId, path: result.filePaths[0] } };
});
ipcMain.handle("attachments:pickFiles", async (event) => {
const result = await mainWindowController.showOpenDialog({
title: "Add attachments",
title: nativeFileDialogCopy(await desktopLocale.resolve()).addAttachments,
properties: ["openFile", "multiSelections"],
});
if (result.canceled || !result.filePaths[0])
Expand Down
9 changes: 7 additions & 2 deletions apps/desktop/src/main/runtime-host-skills-ipc-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import type {
DesktopRuntimeHostClient,
DesktopSkillCatalogSnapshot,
} from "./runtime-host-client.js";
import type { UiLocale } from "@maka/core/ui-locale";
import { nativeFileDialogCopy } from "./native-file-dialog-copy.js";
import { resolveSkillOpenPath } from "./skill-open-path.js";
import {
handleReconnectableRead,
Expand All @@ -68,6 +70,7 @@ interface RuntimeHostSkillsIpcDeps {
readonly getDefaultPermissionMode: () => Promise<ChatDefaultPermissionMode>;
readonly openPath: (path: string) => Promise<string>;
readonly allowLocalPaths?: boolean;
readonly resolveLocale: () => Promise<UiLocale>;
}

interface GovernanceProjection {
Expand Down Expand Up @@ -182,12 +185,14 @@ export function registerRuntimeHostSkillsIpc(
if (deps.allowLocalPaths === false) {
throw new Error("Local Skill import is unavailable for a remote Runtime Host");
}
const copy = nativeFileDialogCopy(await deps.resolveLocale());
const result = await deps.mainWindowController.showOpenDialog({
title: "Import Skill source",
title: copy.importSkillSource,
properties: ["openFile"],
// Format names identify a file format, not a product concept.
filters: [
{ name: "Skill Markdown", extensions: ["md"] },
{ name: "All Files", extensions: ["*"] },
{ name: copy.allFiles, extensions: ["*"] },
],
});
if (result.canceled || result.filePaths.length === 0) {
Expand Down