feat(dashboard): offer AI translation in Import Tasks preview (#2128)
## Summary Import Tasks can now offer on-demand AI translation when a selected GitHub or GitLab issue/PR title and body appear to be in a different language than the active dashboard locale. - Detect foreign-language content with a conservative client heuristic (Unicode scripts + Latin stopwords) - Show an opt-in banner: **Translate**, then **Show original / Show translation**, plus **Dismiss** - Call new `POST /api/ai/translate-text` (shared AI-helper rate limit with refine/draft) - Translation is **display-only** in the preview; imported task text stays the original source language ## Why Operators working in a non-English dashboard (or reading non-dashboard-language issues) needed a way to understand import candidates without leaving the preview or changing what gets imported. ## Test plan - [x] Unit tests for language detection (`detectContentLanguage`) - [x] Unit tests for translate request validation, response parsing, and AI agent path - [x] GitHub import modal: French content shows translate controls; English content does not - [x] Dashboard typecheck clean for app + server packages - [ ] Manual: open Import Tasks with dashboard language English, select a French/Korean issue, translate and toggle original - [ ] Manual: confirm Import still creates the task with original title/body - [ ] Manual: dismiss banner for a selection and confirm it stays dismissed for that item ## Notes - Comments are not translated (title + body only) - zh-CN / zh-TW share a CJK family so Chinese content does not prompt translation when the UI is either Chinese locale - Secondary locale catalogs have empty placeholders for the new `git.translate*` keys (runtime falls back to English)
This commit is contained in:
@@ -6183,6 +6183,77 @@ export async function draftGoalDescription(title: string, projectId?: string): P
|
||||
return response.description;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:GitHubImportTranslate 2026-07-14-12:00:
|
||||
Client for POST /api/ai/translate-text — used by the GitHub/GitLab import preview when issue/PR prose is not the dashboard language.
|
||||
Structured title+body fields keep markdown import content intact; shares the AI-helper rate-limit budget with refine/draft.
|
||||
*/
|
||||
export interface TranslateImportFields {
|
||||
title?: string;
|
||||
body?: string;
|
||||
}
|
||||
|
||||
export interface TranslateImportContentResponse {
|
||||
fields: TranslateImportFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate import-preview title/body into the dashboard locale via AI.
|
||||
* @param fields - Original title and/or body
|
||||
* @param targetLocale - Active dashboard locale
|
||||
* @param projectId - Optional project scope for settings/MCP
|
||||
* @param sourceLocale - Optional detection hint for the model
|
||||
*/
|
||||
export async function translateImportContent(
|
||||
fields: TranslateImportFields,
|
||||
targetLocale: string,
|
||||
projectId?: string,
|
||||
sourceLocale?: string,
|
||||
): Promise<TranslateImportFields> {
|
||||
const response = await api<TranslateImportContentResponse>(
|
||||
withProjectId("/ai/translate-text", projectId),
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
fields,
|
||||
targetLocale,
|
||||
...(sourceLocale ? { sourceLocale } : {}),
|
||||
}),
|
||||
},
|
||||
);
|
||||
return response.fields;
|
||||
}
|
||||
|
||||
/** User-facing error copy for translateImportContent failures (toast/banner). */
|
||||
export const TRANSLATE_ERROR_MESSAGES = {
|
||||
RATE_LIMIT: "Too many translation requests. Please wait an hour.",
|
||||
NETWORK: "Failed to translate content. Please try again.",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Map a translateImportContent error to banner-safe copy.
|
||||
*/
|
||||
export function getTranslateErrorMessage(error: unknown): string {
|
||||
if (!(error instanceof Error)) {
|
||||
return TRANSLATE_ERROR_MESSAGES.NETWORK;
|
||||
}
|
||||
|
||||
const message = error.message.toLowerCase();
|
||||
if (message.includes("rate limit") || message.includes("429")) {
|
||||
return TRANSLATE_ERROR_MESSAGES.RATE_LIMIT;
|
||||
}
|
||||
if (
|
||||
message.startsWith("fields") ||
|
||||
message.startsWith("text to translate") ||
|
||||
message.startsWith("targetlocale") ||
|
||||
message.includes("targetlocale must") ||
|
||||
message.includes("sourceLocale must")
|
||||
) {
|
||||
return error.message;
|
||||
}
|
||||
return TRANSLATE_ERROR_MESSAGES.NETWORK;
|
||||
}
|
||||
|
||||
export function startSubtaskBreakdown(description: string, projectId?: string): Promise<{ sessionId: string }> {
|
||||
return api<{ sessionId: string }>(withProjectId("/subtasks/start-streaming", projectId), {
|
||||
method: "POST",
|
||||
|
||||
Reference in New Issue
Block a user