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
18 changes: 13 additions & 5 deletions apps/desktop/src/main/__tests__/attachment-preflight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ describe('attachment preflight (before session create)', () => {
size: 100,
source: { type: 'file' as const, file: { size: 100 } },
}));
assert.throws(() => preflightAttachmentItems(items), /8/);
assert.throws(() => preflightAttachmentItems(items, 'zh'), /8/);
});

test('rejects an oversized File so no empty session is created', () => {
assert.throws(
() => preflightAttachmentItems([{ size: CAP + 1, source: { type: 'file', file: { size: CAP + 1 } } }]),
() => preflightAttachmentItems([{ size: CAP + 1, source: { type: 'file', file: { size: CAP + 1 } } }], 'zh'),
/50MB/,
);
});

test('rejects an oversized approval-token attachment by pending size', () => {
assert.throws(
() => preflightAttachmentItems([{ size: CAP + 1, source: { type: 'approval', approvalId: 'a1' } }]),
() => preflightAttachmentItems([{ size: CAP + 1, source: { type: 'approval', approvalId: 'a1' } }], 'zh'),
/50MB/,
);
});
Expand All @@ -52,17 +52,25 @@ describe('attachment preflight (before session create)', () => {
preflightAttachmentItems([
{ size: 10, source: { type: 'approval', approvalId: 'dup' } },
{ size: 10, source: { type: 'approval', approvalId: 'dup' } },
]),
], 'zh'),
/重复/,
);
assert.throws(
() =>
preflightAttachmentItems([
{ size: 10, source: { type: 'approval', approvalId: 'dup' } },
{ size: 10, source: { type: 'approval', approvalId: 'dup' } },
], 'en'),
/already added/,
);
});

test('passes approval tokens and files under the cap', () => {
assert.doesNotThrow(() =>
preflightAttachmentItems([
{ size: 100, source: { type: 'approval', approvalId: 'a1' } },
{ size: 100, source: { type: 'file', file: { size: 100 } } },
]),
], 'zh'),
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ describe('renderConversationMarkdown', () => {
modelId: 'fake',
},
];
const md = renderConversationMarkdown('skill session', messages);
const md = renderConversationMarkdown('skill session', messages, 'zh');
assert.match(md, /## 你/);
assert.ok(md.includes(typed), 'export shows the typed prompt');
assert.ok(!md.includes('<invoked-skill'), 'export must not include the skill envelope');
assert.ok(!md.includes('Secret skill body'), 'export must not include skill body');
assert.match(renderConversationMarkdown('skill session', messages, 'en'), /## You/);
});
});
6 changes: 6 additions & 0 deletions apps/desktop/src/main/__tests__/model-catalog-choices.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ describe('model catalog picker helpers', () => {
}),
],
'',
'zh',
);
const keys = options.map(([key]) => key);
assert.ok(
Expand All @@ -198,4 +199,9 @@ describe('model catalog picker helpers', () => {
`unsupported Codex model was offered: ${JSON.stringify(keys)}`,
);
});

it('labels a saved-but-unavailable selection in the UI locale', () => {
const [, label] = buildCatalogDailyReviewModelOptions([], 'codex::gone', 'en').at(-1)!;
assert.equal(label, 'gone · codex · Currently unavailable');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ import { describeTurnErrorClass } from '../../renderer/session-status-presentati

describe('provider capacity presentation', () => {
it('uses capacity-specific copy instead of the unknown error fallback', () => {
assert.match(describeSessionErrorReason('provider_capacity') ?? '', /满载/);
assert.match(describeTurnErrorClass('provider_capacity'), /满载/);
assert.match(describeSessionErrorReason('provider_capacity', 'zh') ?? '', /满载/);
assert.match(describeSessionErrorReason('provider_capacity', 'en') ?? '', /at capacity/);
assert.match(describeTurnErrorClass('provider_capacity', 'zh'), /满载/);
});

it('does not recommend an immediate direct retry', () => {
const label = describeTurnErrorClass('provider_capacity');
const label = describeTurnErrorClass('provider_capacity', 'zh');
assert.match(label, /等几分钟|换一个模型/);
assert.doesNotMatch(label, /直接重试/);
});
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/attachment-preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type PreflightItem = {
* File blobs are sized by the browser File object; approval-token attachments
* are sized by the pending size stamped at pick time (main re-stats).
*/
export function preflightAttachmentItems(items: readonly PreflightItem[], locale: UiLocale = 'zh'): void {
export function preflightAttachmentItems(items: readonly PreflightItem[], locale: UiLocale): void {
const copy = getDesktopConversationCopy(locale).attachments;
if (items.length > MAX_ATTACHMENT_COUNT) throw new Error(copy.tooMany);
const seen = new Set<string>();
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/conversation-markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import { getShellRemainingCopy } from './locales/shell-remaining-copy.js';
* export that the user is going to paste somewhere public.
* - **user text** left untouched (the user typed it, they own it).
*/
export function renderConversationMarkdown(sessionName: string, messages: StoredMessage[], locale: UiLocale = 'zh'): string {
export function renderConversationMarkdown(sessionName: string, messages: StoredMessage[], locale: UiLocale): string {
const copy = getShellRemainingCopy(locale).conversationExport;
const lines: string[] = [];
lines.push(`# ${sessionName}`);
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/renderer/derive-turn-lineage-badges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ export interface TurnLineageBadgeInput {
regeneratedToTurnId?: string;
/** True when the target turn id still exists in the materialized view. */
existsTurn(turnId: string): boolean;
locale?: UiLocale;
locale: UiLocale;
}

export function deriveTurnLineageBadges(input: TurnLineageBadgeInput): TurnLineageBadge[] {
const copy = getDesktopConversationCopy(input.locale ?? 'zh').lineage;
const copy = getDesktopConversationCopy(input.locale).lineage;
const badges: TurnLineageBadge[] = [];

const forwardFrom = input.regeneratedFromTurnId ?? input.retriedFromTurnId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { cleanErrorMessage } from '../../application/contracts/connection-error-

export type CredentialPresenceStatus = boolean | 'loading' | 'error';

export function providerPanelActionErrorMessage(error: unknown, locale: UiLocale = 'zh'): string {
export function providerPanelActionErrorMessage(error: unknown, locale: UiLocale): string {
const shared = getProviderSettingsCopy(locale).shared;
// Electron wraps ipcMain.handle rejections as "Error invoking remote method
// '<channel>': Error: <message>". Classify the original message, not the
Expand Down Expand Up @@ -64,7 +64,7 @@ export interface ConnectionTestTroubleshootingCopy {
export function connectionTestFailureFallback(
result: ConnectionTestResult,
copy: ConnectionTestTroubleshootingCopy,
locale: UiLocale = 'zh',
locale: UiLocale,
): string {
const shared = getProviderSettingsCopy(locale).shared;
if (result.statusCode === 429) return shared.rateLimit;
Expand All @@ -82,7 +82,7 @@ export function connectionTestFailureFallback(
export function connectionTestFailureMessage(
result: ConnectionTestResult,
copy: ConnectionTestTroubleshootingCopy,
locale: UiLocale = 'zh',
locale: UiLocale,
): string {
const fallback = connectionTestFailureFallback(result, copy, locale);
if (!result.errorMessage) return fallback;
Expand All @@ -91,7 +91,7 @@ export function connectionTestFailureMessage(
: generalizedErrorMessage(new Error(result.errorMessage), fallback);
}

export function connectionLastTestMessageDisplay(message: string | undefined, locale: UiLocale = 'zh'): string | undefined {
export function connectionLastTestMessageDisplay(message: string | undefined, locale: UiLocale): string | undefined {
if (!message) return undefined;
const trimmed = message.trim();
if (!trimmed) return undefined;
Expand All @@ -105,6 +105,6 @@ export function connectionLastTestMessageDisplay(message: string | undefined, lo
return classified || copy.statusUnavailable;
}

export function categoryLabel(category: ProviderCategory, locale: UiLocale = 'zh'): string {
export function categoryLabel(category: ProviderCategory, locale: UiLocale): string {
return getProviderSettingsCopy(locale).shared.categories[category];
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const UNGROUPED_KEY = '__ungrouped__';
export function deriveProjectGroups(
sessions: ReadonlyArray<SessionSummary>,
projects: ReadonlyArray<ProjectRecord>,
locale: UiLocale = 'zh',
locale: UiLocale,
): SessionHistoryGroup[] {
const sessionsByProject = new Map<string, SessionSummary[]>();
const canonicalProjectIds = new Map<string, string>();
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/model-catalog-choices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function buildCatalogRecommendedDefaultModel(providerType: ProviderType):
export function buildCatalogDailyReviewModelOptions(
connections: readonly (LlmConnection & HostResolvedConnectionCatalog)[],
currentModelKey: string,
locale: UiLocale = 'zh',
locale: UiLocale,
): Array<readonly [string, string]> {
const current = parseDailyReviewModelKey(currentModelKey);
const candidates: Array<{ key: string; label: string; safeSourceLabel: string }> = [];
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/renderer/model-connection-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function noRealConnectionReasonFromEvent(event: Extract<SessionEvent, { t
).reason;
}

export function noRealConnectionSetupDescription(reason: string | undefined, locale: UiLocale = 'zh'): string {
export function noRealConnectionSetupDescription(reason: string | undefined, locale: UiLocale): string {
const copy = getDesktopConversationCopy(locale).model;
return reason && Object.hasOwn(copy.configurationReason, reason)
? copy.configurationReason[reason as ChatConfigurationReason]
Expand All @@ -55,7 +55,7 @@ export function noRealConnectionSetupDescription(reason: string | undefined, loc

export function sessionEventErrorMessage(
event: Extract<SessionEvent, { type: 'error' }>,
locale: UiLocale = 'zh',
locale: UiLocale,
): string {
if (isNoRealConnectionEvent(event)) {
return noRealConnectionSetupDescription(noRealConnectionReasonFromEvent(event), locale);
Expand All @@ -69,7 +69,7 @@ export function sessionEventErrorMessage(
export function modelSetupToastCopy(
reason: string | undefined,
fallback: string,
locale: UiLocale = 'zh',
locale: UiLocale,
): { title: string; description: string } {
const copy = getDesktopConversationCopy(locale).model;
if (reason === 'connection_missing') {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/session-error-presentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { getDesktopConversationCopy } from './locales/conversation-copy.js';
* runtime. Unknown reasons intentionally return undefined so callers can use
* their existing safe fallback instead of displaying raw provider text.
*/
export function describeSessionErrorReason(reason: string | undefined, locale: UiLocale = 'zh'): string | undefined {
export function describeSessionErrorReason(reason: string | undefined, locale: UiLocale): string | undefined {
const copy = getDesktopConversationCopy(locale).turnError;
switch (reason?.toLowerCase()) {
case 'context_overflow':
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/renderer/session-status-presentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export function normalizeSessionSummaryForDisplay<T extends SessionSummary>(sess
* the UI; they just fall through to the catch-all until the mapping
* is extended.
*/
export function describeTurnErrorClass(errorClass: string | undefined, locale: UiLocale = 'zh'): string {
export function describeTurnErrorClass(errorClass: string | undefined, locale: UiLocale): string {
const copy = getDesktopConversationCopy(locale).turnError;
if (!errorClass) return copy.unknown;
const reasonDescription = describeSessionErrorReason(errorClass, locale);
Expand Down Expand Up @@ -169,7 +169,7 @@ export interface FailedTurnExecutionState {
*/
export function describeFailedTurnExecutionState(
state: FailedTurnExecutionState,
locale: UiLocale = 'zh',
locale: UiLocale,
): string | undefined {
const copy = getDesktopConversationCopy(locale).turnError.executionState;
if (state.erroredToolCount > 0) return copy.erroredTool;
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/renderer/settings/bot-chat-shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const BOT_LABELS: Record<BotProvider, { support: 'runtime' | 'credentials
slack: { support: 'runtime' },
};

export function botReadinessCopyForSupport(support: 'runtime' | 'credentials' | 'planned', readiness: BotReadinessState, locale: UiLocale = 'zh') {
export function botReadinessCopyForSupport(support: 'runtime' | 'credentials' | 'planned', readiness: BotReadinessState, locale: UiLocale) {
const copy = getBotSettingsCopy(locale);
if (support === 'planned') return copy.planned;
return copy.readiness[readiness] ?? copy.readiness.scaffolded;
Expand Down Expand Up @@ -98,7 +98,7 @@ export function BotBrandLogo(props: { provider: BotProvider; size?: 'compact' |
export type BotPendingActionName = 'test' | 'connect' | 'restart' | 'disconnect';
export type BotPendingAction = { provider: BotProvider; action: BotPendingActionName };

export function botStatusDetail(status: BotStatus, locale: UiLocale = 'zh'): string {
export function botStatusDetail(status: BotStatus, locale: UiLocale): string {
const copy = getBotSettingsCopy(locale).status;
switch (status.reason) {
case 'disabled': return copy.disabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export interface ConnectionChipStatus {
* readiness, fixed to credential-only language. Matches the doc warning
* at SettingsModal `验证通过 ≠ 运行可用`.
*/
export function connectionChipStatus(connection: LlmConnection, locale: UiLocale = 'zh'): ConnectionChipStatus | null {
export function connectionChipStatus(connection: LlmConnection, locale: UiLocale): ConnectionChipStatus | null {
const copy = getProviderSettingsCopy(locale).shared.connectionStatuses;
// Ahead of every other branch, including needs_reauth: a retired provider
// has no sign-in left to return to, so any repair-shaped status here would
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/settings/settings-error-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { type UiLocale } from '@maka/core/ui-locale';
import { redactSecrets } from '@maka/ui';
import { getSettingsSharedCopy } from '../locales/settings-shared-copy.js';

export function settingsActionErrorMessage(error: unknown, locale: UiLocale = 'zh'): string {
export function settingsActionErrorMessage(error: unknown, locale: UiLocale): string {
const raw = error instanceof Error
? error.message
: typeof error === 'string'
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/renderer/turn-footer-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export interface TurnFooterContext {
* / other action types stay clickable.
*/
pendingActions?: ReadonlySet<TurnFooterActionId>;
locale?: UiLocale;
locale: UiLocale;
}

/**
Expand All @@ -113,7 +113,7 @@ export interface TurnFooterContext {
*/
export function deriveTurnFooterActions(input: TurnFooterContext): TurnFooterAction[] {
const { status, hasContent, alreadyRegenerated, pendingActions, metaSummary } = input;
const copyText = getDesktopConversationCopy(input.locale ?? 'zh').footer;
const copyText = getDesktopConversationCopy(input.locale).footer;
const actionLabel = copyText.labels;
const isPending = (id: TurnFooterActionId) => pendingActions?.has(id) ?? false;
const PENDING_TOOLTIP = copyText.pending;
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/relative-time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function getAbsoluteFormat(uiLocale: UiLocale): Intl.DateTimeFormat {
* reading a relative label falls back to and a tooltip shows; `@maka/ui` had
* its own uncached copy of the same `Intl` options until this became public.
*/
export function formatAbsoluteTimestamp(ts: number, locale: UiLocale = 'zh'): string {
export function formatAbsoluteTimestamp(ts: number, locale: UiLocale): string {
return getAbsoluteFormat(locale).format(new Date(ts));
}

Expand All @@ -105,7 +105,7 @@ export function formatAbsoluteTimestamp(ts: number, locale: UiLocale = 'zh'): st
export function formatRelativeTimestamp(
ts: number,
now: number = Date.now(),
locale: UiLocale = 'zh',
locale: UiLocale,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: making locale required leaves now: number = Date.now() on the line above unreachable, since no caller can now omit it. Same at lines 160 and 180. I checked every caller (tasks-settings-page.tsx:220, artifact-pane.tsx:509 and :541, the internal call at relative-time.ts:164, and the tests) and all of them already pass now, so the default is dead and only misleads. Either drop = Date.now() from the three signatures, or move locale ahead of now so the default stays reachable.

): string {
const diffMs = relativeAgeMs(ts, now);
if (diffMs < JUST_NOW_MS) {
Expand Down Expand Up @@ -158,7 +158,7 @@ function getCompactFormats(uiLocale: UiLocale): {
export function formatCompactTimestamp(
ts: number,
now: number = Date.now(),
locale: UiLocale = 'zh',
locale: UiLocale,
): string {
const diffMs = relativeAgeMs(ts, now);
if (diffMs <= RELATIVE_HORIZON_MS) return formatRelativeTimestamp(ts, now, locale);
Expand All @@ -178,7 +178,7 @@ export function formatCompactTimestamp(
export function formatSidebarTimestamp(
ts: number,
now: number = Date.now(),
locale: UiLocale = 'zh',
locale: UiLocale,
): string {
const diffMs = relativeAgeMs(ts, now);
if (diffMs < JUST_NOW_MS) return JUST_NOW[locale];
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/tool-quiet-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export interface ToolInvocationInput {
*/
export function formatToolInvocationLine(
item: ToolInvocationInput,
locale: UiLocale = 'zh',
locale: UiLocale,
): string | undefined {
const s = strings(locale);
const args = asRecord(item.args);
Expand Down Expand Up @@ -518,7 +518,7 @@ export interface QuietPreview {
* Primary list/text fields become the main body; remaining fields (error, ok,
* truncated, …) are appended so diagnostics cannot vanish.
*/
export function formatQuietJsonValue(value: unknown, locale: UiLocale = 'zh'): QuietPreview {
export function formatQuietJsonValue(value: unknown, locale: UiLocale): QuietPreview {
const s = strings(locale);
if (value === null || value === undefined) {
return { body: s.empty };
Expand Down Expand Up @@ -677,7 +677,7 @@ function formatArrayAsBody(values: unknown[], locale: UiLocale): string {
export function formatAsKeyValueLines(
record: Record<string, unknown>,
depth = 0,
locale: UiLocale = 'zh',
locale: UiLocale,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: same as relative-time. depth = 0 on the line above is now unreachable because locale follows it, and all four internal callers (lines 315, 605, 651, 664) already pass 0 explicitly. Drop the default or reorder the parameters.

): string {
const s = strings(locale);
if (depth > 3) return redactSecrets(String(record));
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/artifact-preview-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function exceedsImagePayloadCap(base64: string): boolean {
return base64.length > IMAGE_PAYLOAD_MAX_BASE64_LENGTH;
}

export function formatPreviewSize(sizeBytes: number | undefined, locale: UiLocale = 'zh'): string {
export function formatPreviewSize(sizeBytes: number | undefined, locale: UiLocale): string {
if (sizeBytes === undefined || sizeBytes < 0 || !Number.isFinite(sizeBytes)) return getSharedUiCopy(locale).artifact.unknownSize;
if (sizeBytes < 1024) return `${sizeBytes} B`;
if (sizeBytes < 1024 * 1024) return `${(sizeBytes / 1024).toFixed(1)} KB`;
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/chat-model-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export type { ChatModelChoice } from '@maka/core/chat-model-choice';

export function modelChoiceDescription(
choice: Pick<ChatModelChoice, 'description' | 'knowledgeCutoff'>,
locale: UiLocale = 'zh',
locale: UiLocale,
): string | undefined {
const description = choice.description?.trim();
const knowledge = choice.knowledgeCutoff?.trim();
Expand Down Expand Up @@ -85,7 +85,7 @@ export interface ModelMenuGroup {
* account email `connection.name` carries for `claude-subscription` /
* `openai-codex`.
*/
export function modelMenuGroups(choices: ChatModelChoice[], locale: UiLocale = 'zh'): ModelMenuGroup[] {
export function modelMenuGroups(choices: ChatModelChoice[], locale: UiLocale): ModelMenuGroup[] {
const copy = getSharedUiCopy(locale).providers;
const localizedLabels: Partial<Record<ProviderType, string>> = {
'MiniMax-cn': copy.minimaxChina,
Expand Down
Loading