diff --git a/src/testing/create-plugin-module.test.ts b/src/testing/create-plugin-module.test.ts new file mode 100644 index 000000000..94163ee3f --- /dev/null +++ b/src/testing/create-plugin-module.test.ts @@ -0,0 +1,103 @@ +import { beforeEach, describe, expect, it, mock } from "bun:test" +import { getLocale, initI18n, t } from "../shared/i18n" +import { createPluginModule } from "./create-plugin-module" + +const mockInitConfigContext = mock(() => {}) +const mockDetectExternalSkillPlugin = mock(() => ({ detected: false, pluginName: null, allPlugins: [] })) +const mockGetSkillPluginConflictWarning = mock(() => "") +const mockInjectServerAuthIntoClient = mock(() => {}) +const mockLogLegacyPluginStartupWarning = mock(() => {}) +const mockMigrateLegacyWorkspaceDirectory = mock(() => ({ migrated: false, skipped: [] })) +const mockLoadPluginConfig = mock(() => ({})) +const mockIsTmuxIntegrationEnabled = mock( + (pluginConfig: { tmux?: { enabled?: boolean } | undefined }) => pluginConfig.tmux?.enabled ?? false, +) +const mockCreateRuntimeTmuxConfig = mock(() => ({ + enabled: false, + layout: "tiled" as const, + main_pane_size: 60, + main_pane_min_width: 80, + agent_pane_min_width: 40, + isolation: "inline" as const, +})) +const mockCreateManagers = mock(() => ({ + backgroundManager: { shutdown: async () => {} }, + skillMcpManager: { disconnectAll: async () => {} }, + configHandler: async () => {}, +})) +const mockCreateTools = mock(async () => ({ + mergedSkills: [], + availableSkills: [], + filteredTools: {}, +})) +const mockCreateHooks = mock(() => ({ + disposeHooks: () => {}, + compactionContextInjector: undefined, + compactionTodoPreserver: undefined, + claudeCodeHooks: undefined, +})) +const mockCreatePluginInterface = mock(() => ({})) +const mockInitializeOpenClaw = mock(async () => {}) +const mockStartTmuxCheck = mock(() => {}) +const mockInstallAgentSortShim = mock(() => {}) +const mockSetAgentSortOrder = mock(() => {}) +const mockLog = mock(() => {}) +const mockCreateModelCacheState = mock(() => ({})) +const mockCreateFirstMessageVariantGate = mock(() => ({ + shouldOverride: () => false, + markApplied: () => {}, + markSessionCreated: () => {}, + clear: () => {}, +})) + +function createTestPluginModule(): ReturnType { + return createPluginModule({ + initConfigContext: mockInitConfigContext, + detectExternalSkillPlugin: mockDetectExternalSkillPlugin, + getSkillPluginConflictWarning: mockGetSkillPluginConflictWarning, + injectServerAuthIntoClient: mockInjectServerAuthIntoClient, + logLegacyPluginStartupWarning: mockLogLegacyPluginStartupWarning, + migrateLegacyWorkspaceDirectory: mockMigrateLegacyWorkspaceDirectory, + loadPluginConfig: mockLoadPluginConfig as never, + isTmuxIntegrationEnabled: mockIsTmuxIntegrationEnabled as never, + createRuntimeTmuxConfig: mockCreateRuntimeTmuxConfig as never, + createManagers: mockCreateManagers as never, + createTools: mockCreateTools as never, + createHooks: mockCreateHooks as never, + createPluginInterface: mockCreatePluginInterface as never, + initializeOpenClaw: mockInitializeOpenClaw as never, + startTmuxCheck: mockStartTmuxCheck, + installAgentSortShim: mockInstallAgentSortShim, + setAgentSortOrder: mockSetAgentSortOrder, + log: mockLog, + createModelCacheState: mockCreateModelCacheState as never, + createFirstMessageVariantGate: mockCreateFirstMessageVariantGate as never, + }) +} + +describe("createPluginModule()", () => { + beforeEach(() => { + mockLoadPluginConfig.mockClear() + initI18n({ locale: "en", fallback: "en" }) + }) + + describe("#given plugin config sets i18n.locale to zh", () => { + it("#then production startup applies the configured locale", async () => { + // given + const pluginModule = createTestPluginModule() + mockLoadPluginConfig.mockReturnValue({ + i18n: { locale: "zh" }, + }) + + // when + await pluginModule.server({ + directory: "/tmp/project", + client: {}, + } as Parameters[0]) + + // then + expect(getLocale()).toBe("zh") + expect(t("toast.task_completed")).toBe("任务完成") + }) + }) +}) diff --git a/src/testing/create-plugin-module.ts b/src/testing/create-plugin-module.ts index 684890976..b7f58bcda 100644 --- a/src/testing/create-plugin-module.ts +++ b/src/testing/create-plugin-module.ts @@ -18,6 +18,7 @@ import { import { installAgentSortShim, setAgentSortOrder } from "../shared/agent-sort-shim" import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "../shared/external-plugin-detector" import { createFirstMessageVariantGate } from "../shared/first-message-variant" +import { initI18n } from "../shared/i18n" import { log } from "../shared/logger" import { logLegacyPluginStartupWarning } from "../shared/log-legacy-plugin-startup-warning" import { migrateLegacyWorkspaceDirectory } from "../shared/legacy-workspace-migration" @@ -39,6 +40,7 @@ export type PluginModuleDeps = { getSkillPluginConflictWarning: typeof getSkillPluginConflictWarning injectServerAuthIntoClient: typeof injectServerAuthIntoClient loadPluginConfig: typeof loadPluginConfig + initI18n: typeof initI18n initializeOpenClaw: typeof initializeOpenClaw isTmuxIntegrationEnabled: typeof isTmuxIntegrationEnabled startTmuxCheck: typeof startTmuxCheck @@ -62,6 +64,7 @@ const defaultPluginModuleDeps: PluginModuleDeps = { getSkillPluginConflictWarning, injectServerAuthIntoClient, loadPluginConfig, + initI18n, initializeOpenClaw, isTmuxIntegrationEnabled, startTmuxCheck, @@ -93,6 +96,7 @@ export function createPluginModule(overrides: Partial = {}): P deps.injectServerAuthIntoClient(input.client) const pluginConfig = deps.loadPluginConfig(input.directory, input) + deps.initI18n(pluginConfig.i18n?.locale ? { locale: pluginConfig.i18n.locale } : undefined) deps.setAgentSortOrder(pluginConfig.agent_order) if (pluginConfig.openclaw) {