Pin the UI locale to the language setting and add theme + language selectors with a dark palette
This commit is contained in:
1 parent
b30b9a3bc1
commit
d404e1e72a
18 files changed
+176
-11
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { dialog, ipcMain, shell, type BrowserWindow } from "electron";
|
||||
import { app, dialog, ipcMain, shell, type BrowserWindow } from "electron";
|
||||
import type { DatabaseConnection } from "../database/connection";
|
||||
import { IPC_CHANNELS } from "../../shared/ipc";
|
||||
import { fail, ok, type DesktopResult } from "../../shared/result";
|
||||
@@ -107,6 +107,19 @@ export function registerIpcHandlers(deps: IpcDeps): void {
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.createOrganization, (_event, input) => mutate(() => organization.createOrganization(input)));
|
||||
ipcMain.handle(IPC_CHANNELS.setActiveOrganization, (_event, input) => mutate(() => organization.setActiveOrganization(input.orgId)));
|
||||
ipcMain.handle(IPC_CHANNELS.setUiPreferences, (_event, input) => {
|
||||
const before = snapshot.load().uiPreferences.language;
|
||||
const result = mutate(() => organization.updateUiPreferences(input));
|
||||
if (result.ok && result.data.uiPreferences.language !== before) {
|
||||
// Chromium's locale is fixed at launch; a language change needs a
|
||||
// fresh process to take effect.
|
||||
setTimeout(() => {
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
}, 150);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.deleteOrganization, (_event, input) => mutate(() => organization.deleteOrganization(input.orgId)));
|
||||
ipcMain.handle(IPC_CHANNELS.createAgent, (_event, input) => mutate(() => organization.createAgent(input)));
|
||||
ipcMain.handle(IPC_CHANNELS.updateAgent, (_event, input) => mutate(() => organization.updateAgent(input)));
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* App-level UI preferences on the single app_state row. Theme drives the
|
||||
* renderer palette; language pins Chromium's UI locale (form-control strings
|
||||
* were following the OS locale and leaking Chinese into the English UI, so
|
||||
* the default is English rather than system).
|
||||
*/
|
||||
export const uiPreferencesSchema = `
|
||||
alter table app_state add column theme text not null default 'system';
|
||||
alter table app_state add column language text not null default 'en';
|
||||
`;
|
||||
@@ -46,6 +46,7 @@ import { mentionWakeStateSchema } from "./migrations/044-mention-wake-state";
|
||||
import { connectorGrantsSchema } from "./migrations/045-connector-grants";
|
||||
import { composioKeyPurgeSchema } from "./migrations/046-composio-key-purge";
|
||||
import { authConfigPurgeSchema } from "./migrations/047-auth-config-purge";
|
||||
import { uiPreferencesSchema } from "./migrations/048-ui-preferences";
|
||||
|
||||
interface Migration {
|
||||
version: number;
|
||||
@@ -101,6 +102,7 @@ const MIGRATIONS: Migration[] = [
|
||||
{ version: 45, name: "connector_grants", sql: connectorGrantsSchema },
|
||||
{ version: 46, name: "composio_key_purge", sql: composioKeyPurgeSchema },
|
||||
{ version: 47, name: "auth_config_purge", sql: authConfigPurgeSchema },
|
||||
{ version: 48, name: "ui_preferences", sql: uiPreferencesSchema },
|
||||
];
|
||||
|
||||
const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
|
||||
|
||||
+12
-2
@@ -18,9 +18,14 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit();
|
||||
} else {
|
||||
const profileDir = process.env.AGENTCOMPANY_NEW_PROFILE_DIR ?? join(app.getPath("userData"), "profile");
|
||||
// The database opens before ready: Chromium's UI locale (form-control
|
||||
// strings, date formatting) is only settable pre-ready, and it follows the
|
||||
// stored language preference instead of the OS locale.
|
||||
const connection = new DatabaseConnection(join(profileDir, "agentcompany.db"));
|
||||
app.commandLine.appendSwitch("lang", storedLanguage(connection.db) === "zh" ? "zh-CN" : "en-US");
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const profileDir = process.env.AGENTCOMPANY_NEW_PROFILE_DIR ?? join(app.getPath("userData"), "profile");
|
||||
const connection = new DatabaseConnection(join(profileDir, "agentcompany.db"));
|
||||
connection.transaction(() => recoverInterruptedWork(connection.db));
|
||||
const win = createMainWindow({
|
||||
preloadPath: join(__dirname, "../preload/index.js"),
|
||||
@@ -60,6 +65,11 @@ if (!app.requestSingleInstanceLock()) {
|
||||
});
|
||||
}
|
||||
|
||||
function storedLanguage(db: DatabaseSync): string {
|
||||
const row = db.prepare("select language from app_state where id = 1").get() as { language: string } | undefined;
|
||||
return row?.language ?? "en";
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the on-disk skill library for every organization at startup. One
|
||||
* organization's failure (a broken workspace path, say) must not block the
|
||||
|
||||
@@ -47,6 +47,18 @@ export class OrganizationService {
|
||||
});
|
||||
}
|
||||
|
||||
updateUiPreferences(input: { theme?: "system" | "light" | "dark"; language?: "en" | "zh" }): void {
|
||||
const current = this.db.prepare("select theme, language from app_state where id = 1")
|
||||
.get() as { theme: string; language: string } | undefined;
|
||||
const theme = input.theme ?? current?.theme ?? "system";
|
||||
const language = input.language ?? current?.language ?? "en";
|
||||
this.db.prepare(`
|
||||
insert into app_state(id, active_org_id, theme, language, updated_at)
|
||||
values (1, null, ?, ?, ?)
|
||||
on conflict(id) do update set theme = excluded.theme, language = excluded.language, updated_at = excluded.updated_at
|
||||
`).run(theme, language, new Date().toISOString());
|
||||
}
|
||||
|
||||
deleteOrganization(orgId: string): void {
|
||||
const row = this.db.prepare("select id from organizations where id = ?")
|
||||
.get(orgId) as { id: string } | undefined;
|
||||
|
||||
@@ -87,6 +87,7 @@ export class SnapshotService {
|
||||
|
||||
return {
|
||||
activeOrgId,
|
||||
uiPreferences: this.resolveUiPreferences(),
|
||||
organizations,
|
||||
employees: list(this.db, "employees", "created_at asc"),
|
||||
agents,
|
||||
@@ -178,6 +179,14 @@ export class SnapshotService {
|
||||
if (stored && known.includes(stored)) return stored;
|
||||
return known[0] ?? null;
|
||||
}
|
||||
|
||||
private resolveUiPreferences(): AppSnapshot["uiPreferences"] {
|
||||
const row = this.db.prepare("select theme, language from app_state where id = 1")
|
||||
.get() as { theme: string; language: string } | undefined;
|
||||
const theme = row?.theme === "light" || row?.theme === "dark" ? row.theme : "system";
|
||||
const language = row?.language === "zh" ? "zh" : "en";
|
||||
return { theme, language };
|
||||
}
|
||||
}
|
||||
|
||||
function buildProviderRateLimits(
|
||||
|
||||
Reference in new issue
Block a user