FN-8304: detect foreign issue-form content for translation
Recognize foreign GitHub and GitLab issue-form prose so import translation remains available. - Remove issue-form scaffolding before content-language scoring. - Identify Czech as unsupported foreign Latin content and offer translation. - Cover automatic and manual translation paths with issue-form fixtures. - Document the expanded import translation behavior and add a patch changeset. Files changed: .changeset/fn-8304-issue-form-translation.md | 7 ++ docs/dashboard-guide.md | 2 +- docs/settings-reference.md | 2 +- packages/core/src/detect-content-language.ts | 66 ++++++++++-- .../__tests__/GitHubImportAutoTranslate.test.tsx | 42 +++++++- .../utils/__tests__/detectContentLanguage.test.ts | 120 +++++++++++++++++++++ .../src/__tests__/import-translate-service.test.ts | 56 ++++++++++ 7 files changed, 284 insertions(+), 11 deletions(-) Fusion-Task-Id: FN-8304 Fusion-Task-Lineage: ef6ca70c-aa67-40ff-a23a-077940b5a5f0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8304-issue-form-translation.md
Normal file
7
.changeset/fn-8304-issue-form-translation.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Foreign-language GitHub/GitLab issues authored via issue forms now auto-translate and offer the Translate button.
|
||||
category: fix
|
||||
dev: detectContentLanguage now strips issue-form scaffolding line-by-line (headers, standalone bold field-label lines, checkboxes, `_No response_`, HTML comments) and strips only the leading `>` blockquote marker while retaining quoted content, before script/stopword scoring, so form bodies are no longer scored as English and skipped by both the server auto-translate eligibility (isTranslatable) and the client offer path. Stripping is line-scoped so inline bold/list/quote content in ordinary prose (triage/ai-summary inputs) is preserved.
|
||||
@@ -337,7 +337,7 @@ Use Import Tasks on desktop/tablet:
|
||||
3. Stay on **Issues** or switch to **Pull Requests**, then optionally enter issue label filters before loading results.
|
||||
Expected outcome: the list pane shows matching open issues or pull requests and marks entries that already exist on the board. Use **Hide imported** beside the imported count to remove those unavailable rows from the current Issues, Pull Requests, or GitLab list; turning it off restores the greyed **Imported** rows. After a successful GitHub or GitLab import, the source row is marked **Imported** and made unavailable immediately, without waiting for the board list to refresh.
|
||||
4. Select an issue or pull request row.
|
||||
Expected outcome: the full-width candidate list stays visible while its title, source link, body, labels or PR metadata, and import controls open in a draggable and resizable detail window. On mobile, that detail is a full-screen sheet. When selected title/body content is in another language, the detail offers **Translate**, **Show original** / **Show translation**, and **Dismiss**; translation is display-only. With GitHub import auto-translate enabled, every reachable page of open GitHub issues is translated as you page through the fetched list (up to the 300-issue fetch cap per one-hour translate budget); repeat views use the translation cache. While a page is translating, the issues list shows a **Translating…** status indicator and surfaces any translation failure without blocking import or browsing. Pull request and GitLab lists retain the per-selection translation flow. A pull request preview also shows its checks; use **Refresh checks** to fetch current GitHub check status and comments without reopening the detail. Each failed check has a **Create fix task** action that creates a new task prefilled with the repository, PR, branches, check status, and check-details link.
|
||||
Expected outcome: the full-width candidate list stays visible while its title, source link, body, labels or PR metadata, and import controls open in a draggable and resizable detail window. On mobile, that detail is a full-screen sheet. When selected title/body content is in another language, the detail offers **Translate**, **Show original** / **Show translation**, and **Dismiss**; translation is display-only. Issue forms are evaluated from their user-provided answers rather than template headings, labels, placeholders, and checkboxes, so foreign-language form content receives the same translation offer. With GitHub import auto-translate enabled, every reachable page of open GitHub issues is translated as you page through the fetched list (up to the 300-issue fetch cap per one-hour translate budget); repeat views use the translation cache. While a page is translating, the issues list shows a **Translating…** status indicator and surfaces any translation failure without blocking import or browsing. Pull request and GitLab lists retain the per-selection translation flow. A pull request preview also shows its checks; use **Refresh checks** to fetch current GitHub check status and comments without reopening the detail. Each failed check has a **Create fix task** action that creates a new task prefilled with the repository, PR, branches, check status, and check-details link.
|
||||
<!--
|
||||
FNXC:GitHubImportDocs 2026-07-17-12:00:
|
||||
Import Tasks documentation distinguishes Add comment (an upstream GitHub mutation) from Import as task
|
||||
|
||||
@@ -654,7 +654,7 @@ Default notes:
|
||||
| `githubLinkImportedIssuesToTracking` | `boolean` | `false` | Project-scoped, import-only option. When enabled, GitHub issue imports from the dashboard, CLI, and extension tools persist `githubTracking: { enabled: true }` so Fusion adopts the imported source issue as the tracking issue without turning tracking on for ordinary new tasks. Duplicate/skipped imports do not create tasks or tracking metadata. |
|
||||
| `reportMode` | `"draft-review" \| "auto-file"` | `"draft-review"` | Default in-app Bug, Feedback, Idea, and Help report behavior. `"draft-review"` presents the scrubbed, structured draft for approval; `"auto-file"` files a new report or endorses a strong open duplicate automatically. Dashboard location: **Settings → Project → General → In-app report mode**. |
|
||||
| `reportModeByAction` | `Partial<Record<"bug" \| "feedback" \| "idea" \| "help", "draft-review" \| "auto-file">>` | `undefined` | Optional per-action override map. An action-specific value takes precedence over `reportMode`; unset actions use the project default. Every mode always scrubs report content before GitHub egress. |
|
||||
| `githubImportAutoTranslate` | `boolean` | `false` | Project-scoped, import-only option. When enabled, the Import Tasks panel automatically translates foreign-language **GitHub open issue** titles and bodies into `importTranslateTargetLocale` and shows the translation by default (the original text stays one toggle away). Translation follows each reachable page of the fetched list (up to the 300-issue fetch cap in one hour); cached repeat views do not spend model calls. Pull requests and GitLab retain per-selection translation. Translations persist across app restarts until the upstream issue changes or closes. Off by default so all-English projects never pay for a per-issue AI call. Dashboard location: **Settings → Project → General → GitHub Tracking**. |
|
||||
| `githubImportAutoTranslate` | `boolean` | `false` | Project-scoped, import-only option. When enabled, the Import Tasks panel automatically translates foreign-language **GitHub open issue** titles and bodies into `importTranslateTargetLocale` and shows the translation by default (the original text stays one toggle away). Translation follows each reachable page of the fetched list (up to the 300-issue fetch cap in one hour); cached repeat views do not spend model calls. Pull requests and GitLab retain per-selection translation. Translations persist across app restarts until the upstream issue changes or closes. Off by default so all-English projects never pay for a per-issue AI call. Issue-form template scaffolding (headings, field labels, placeholders, and checkboxes) is ignored when detecting an issue's language, so foreign-language answers remain eligible. Dashboard location: **Settings → Project → General → GitHub Tracking**. |
|
||||
| `importTranslateTargetLocale` | `Locale` | `undefined` | Target language for `githubImportAutoTranslate`. One of `SUPPORTED_LOCALES`. When unset, import translation follows the dashboard's own `language` setting. Dashboard location: **Settings → Project → General → GitHub Tracking**. |
|
||||
| `githubTrackingDefaultRepo` | `string` | `undefined` | Project default issue-tracking repo (`owner/repo`) used before global fallback for tracked task creation (precedence: task override → project default → global default). In Settings UI this is a detected-remote dropdown with a Custom fallback for manual entry. This key is dual-scope: project saves go through `PUT /api/settings` (Settings → General → GitHub Tracking) while global saves go through `PUT /api/settings/global` (Settings → Global General). |
|
||||
| `gitlabEnabled` | `boolean` | `undefined` (effective global fallback, then `true`) | Project GitLab integration enable switch. Explicit `false` disables outbound GitLab API imports, completion comments, close/reopen, source closed-at backfill, and tracking refresh side effects for this project without deleting saved URL/token fields. Dashboard location: **Settings → Project → General → GitLab Configuration** and **Settings → Project → Merge → GitLab Authentication** disclosure headers. |
|
||||
|
||||
@@ -28,7 +28,7 @@ export type DetectedContentLanguage = {
|
||||
confidence: "high" | "medium" | "low";
|
||||
};
|
||||
|
||||
const LATIN_STOPWORDS: Record<"en" | "fr" | "es", readonly string[]> = {
|
||||
const LATIN_STOPWORDS: Record<"en" | "fr" | "es" | "cs", readonly string[]> = {
|
||||
en: [
|
||||
"the", "and", "for", "that", "with", "this", "from", "have", "will", "are",
|
||||
"not", "but", "you", "all", "can", "has", "was", "were", "been", "their",
|
||||
@@ -44,6 +44,17 @@ const LATIN_STOPWORDS: Record<"en" | "fr" | "es", readonly string[]> = {
|
||||
"este", "esta", "está", "son", "pero", "sus", "sobre", "entre", "cuando",
|
||||
"también", "después", "desde", "hasta", "sin", "todos", "puede",
|
||||
],
|
||||
/*
|
||||
FNXC:GitHubImportTranslate 2026-07-18-19:05:
|
||||
Czech is not a dashboard locale, but issue #2306 showed it must still be recognized as confidently
|
||||
foreign Latin prose. Keep its result as `unknown` rather than pretending it is French or Spanish;
|
||||
contentNeedsTranslation can then offer translation for a known-foreign, unsupported source language.
|
||||
*/
|
||||
cs: [
|
||||
"aby", "ale", "bez", "byla", "byl", "by", "co", "do", "jak", "jako", "je", "jsou",
|
||||
"kdy", "ktere", "ktery", "na", "nebo", "nema", "neni", "po", "pro", "pri", "se", "si",
|
||||
"tak", "to", "uz", "ve", "vse", "z", "ze",
|
||||
],
|
||||
};
|
||||
|
||||
function countMatches(text: string, re: RegExp): number {
|
||||
@@ -65,7 +76,7 @@ function isSupportedLocale(value: string): value is Locale {
|
||||
* Score Latin text against en/fr/es stopword lists.
|
||||
* Returns the best locale and a confidence derived from score separation.
|
||||
*/
|
||||
function scoreLatinLocale(text: string): { locale: Locale; confidence: DetectedContentLanguage["confidence"] } {
|
||||
function scoreLatinLocale(text: string): { locale: Locale | "unknown"; confidence: DetectedContentLanguage["confidence"] } {
|
||||
const tokens = text
|
||||
.toLowerCase()
|
||||
.normalize("NFKD")
|
||||
@@ -77,16 +88,16 @@ function scoreLatinLocale(text: string): { locale: Locale; confidence: DetectedC
|
||||
return { locale: "en", confidence: "low" };
|
||||
}
|
||||
|
||||
const scores: Record<"en" | "fr" | "es", number> = { en: 0, fr: 0, es: 0 };
|
||||
const scores: Record<"en" | "fr" | "es" | "cs", number> = { en: 0, fr: 0, es: 0, cs: 0 };
|
||||
for (const token of tokens) {
|
||||
for (const locale of ["en", "fr", "es"] as const) {
|
||||
for (const locale of ["en", "fr", "es", "cs"] as const) {
|
||||
if (LATIN_STOPWORDS[locale].includes(token)) {
|
||||
scores[locale] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ranked = (Object.entries(scores) as Array<["en" | "fr" | "es", number]>).sort(
|
||||
const ranked = (Object.entries(scores) as Array<["en" | "fr" | "es" | "cs", number]>).sort(
|
||||
(a, b) => b[1] - a[1],
|
||||
);
|
||||
const [best, second] = ranked;
|
||||
@@ -101,7 +112,7 @@ function scoreLatinLocale(text: string): { locale: Locale; confidence: DetectedC
|
||||
const confidence: DetectedContentLanguage["confidence"] =
|
||||
bestScore >= 4 && ratio >= 1.6 ? "high" : bestScore >= 2 && ratio >= 1.25 ? "medium" : "low";
|
||||
|
||||
return { locale: best[0], confidence };
|
||||
return { locale: best[0] === "cs" ? "unknown" : best[0], confidence };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,13 +125,42 @@ export function detectContentLanguage(text: string): DetectedContentLanguage {
|
||||
return { locale: "unknown", family: "other", confidence: "low" };
|
||||
}
|
||||
|
||||
// Strip fenced code, URLs, and GitHub usernames so detection focuses on prose.
|
||||
/*
|
||||
FNXC:GitHubImportTranslate 2026-07-18-11:30:
|
||||
GitHub/GitLab issue forms surround user answers with English headers, standalone field labels,
|
||||
checkboxes, `_No response_`, and comments. Issue #2306 showed that scaffolding could outweigh
|
||||
foreign prose, making both server auto-translation and the preview offer incorrectly skip it.
|
||||
Strip whole scaffold lines only after a form signature identifies the body, preserving headings
|
||||
and task-list prose in ordinary issues. Blockquotes retain their content (only the `>` marker is
|
||||
removed), so free-form triage and summary prose is not gutted.
|
||||
*/
|
||||
const formLines = sample.split(/\r?\n/);
|
||||
const standaloneBoldLabels = formLines.filter((line) => /^\s*\*\*[^*\n]+\*\*\s*:?\s*$/.test(line));
|
||||
const hasFormHeaders = formLines.some((line) => /^\s*#{1,6}\s+.*$/.test(line));
|
||||
const hasFormPlaceholder = formLines.some((line) => /^\s*_No response_\s*$/i.test(line));
|
||||
const hasFormCheckbox = formLines.some((line) => /^\s*[-*]\s*\[[ xX]\]\s*.*$/.test(line));
|
||||
const hasIssueFormScaffolding =
|
||||
hasFormHeaders &&
|
||||
(standaloneBoldLabels.length >= 2 || (hasFormPlaceholder && (standaloneBoldLabels.length >= 1 || hasFormCheckbox)));
|
||||
|
||||
const cleaned = sample
|
||||
.replace(/<!--[\s\S]*?-->/g, " ")
|
||||
.replace(/```[\s\S]*?```/g, " ")
|
||||
.replace(/`[^`]+`/g, " ")
|
||||
.replace(/https?:\/\/\S+/gi, " ")
|
||||
.replace(/@[\w-]+/g, " ")
|
||||
.replace(/#\d+/g, " ");
|
||||
.replace(/#\d+/g, " ")
|
||||
.split(/\r?\n/)
|
||||
.flatMap((line) => {
|
||||
// Quoted user prose is content; do not apply form-scaffold filtering to it.
|
||||
if (/^\s*>\s?/.test(line)) return [line.replace(/^\s*>\s?/, "")];
|
||||
if (hasIssueFormScaffolding && /^\s*#{1,6}\s+.*$/.test(line)) return [];
|
||||
if (hasIssueFormScaffolding && /^\s*\*\*[^*\n]+\*\*\s*:?\s*$/.test(line)) return [];
|
||||
if (hasIssueFormScaffolding && /^\s*[-*]\s*\[[ xX]\]\s*.*$/.test(line)) return [];
|
||||
if (hasIssueFormScaffolding && /^\s*_No response_\s*$/i.test(line)) return [];
|
||||
return [line];
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const hangul = countMatches(cleaned, /[\uAC00-\uD7AF]/g);
|
||||
const hiraganaKatakana = countMatches(cleaned, /[\u3040-\u30FF]/g);
|
||||
@@ -185,6 +225,16 @@ export function contentNeedsTranslation(
|
||||
if (detected.confidence === "high" && detected.family !== familyForLocale(dashboardLocale) && detected.family !== "other") {
|
||||
return { needed: true, detected };
|
||||
}
|
||||
/*
|
||||
FNXC:GitHubImportTranslate 2026-07-18-19:05:
|
||||
An unsupported Latin language with a high-confidence stopword match (for example Czech in
|
||||
issue #2306) has no safe supported locale label, but it is foreign to every supported dashboard
|
||||
Latin locale. Offer translation rather than treating `unknown` as English and skipping both
|
||||
import auto-translation and the preview CTA.
|
||||
*/
|
||||
if (detected.confidence === "high" && detected.family === "latin") {
|
||||
return { needed: true, detected };
|
||||
}
|
||||
return { needed: false, detected };
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ awaits the run. These pin that contract — a regression to a single all-or-noth
|
||||
as "no translations until every issue finishes", which is exactly what these detect.
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { renderHook, waitFor, cleanup } from "@testing-library/react";
|
||||
import { render, renderHook, waitFor, cleanup } from "@testing-library/react";
|
||||
|
||||
const { autoTranslateImportIssues } = vi.hoisted(() => ({ autoTranslateImportIssues: vi.fn() }));
|
||||
|
||||
@@ -28,6 +28,7 @@ vi.mock("react-i18next", () => ({
|
||||
|
||||
import {
|
||||
useGitHubImportAutoTranslate,
|
||||
useGitHubImportTranslation,
|
||||
AUTO_TRANSLATE_CHUNK_SIZE,
|
||||
hashImportItemsForKey,
|
||||
} from "../GitHubImportTranslateControls";
|
||||
@@ -67,6 +68,29 @@ afterEach(() => {
|
||||
|
||||
const base = { enabled: true, owner: "o", repo: "r", reloadGeneration: 0, targetLocale: "en" as const };
|
||||
|
||||
const REPORTED_CZECH_ISSUE_FORM = `
|
||||
# PWA na iOS: studený start bez tokenu skončí ve smyčce „Can't reach Fusion Backend" — dialog pro vložení tokenu se nikdy nezobrazí
|
||||
|
||||
**GitHub issue:** Runfusion/Fusion#TBD
|
||||
**Verze Fusion:** 0.72.0 (zdrojový checkout)
|
||||
**Oblast:** Dashboard / autentizace (PWA, vzdálený přístup přes tunel)
|
||||
**Závažnost:** Vysoká — aplikaci přidanou na plochu iPhonu nelze vůbec autorizovat.
|
||||
|
||||
## Shrnutí
|
||||
|
||||
Dashboard zpřístupněný přes Remote Access funguje v mobilním Safari správně. Token přiteče přes přihlašovací URL a uloží se do localStorage. Po přidání na plochu na iOS ale instalovaná webová aplikace startuje bez tokenu v URL, běží v izolovaném úložišti a místo dialogu pro vložení tokenu zobrazí jen chybovou stránku Unauthorized.
|
||||
|
||||
## Reprodukce
|
||||
|
||||
1. Spusťte dashboard s aktivní bearer-token autentizací a zpřístupněte ho přes tunel.
|
||||
2. Na iPhonu otevřete přihlašovací URL v Safari a potom aplikaci přidejte na plochu.
|
||||
3. Otevřete aplikaci z plochy: zobrazí se chyba Unauthorized a tlačítko Retry Connection nic nedělá.
|
||||
|
||||
## Očekávané chování
|
||||
|
||||
Nepřihlášený studený start nabídne vložení tokenu, aby aplikace nebyla trvale nepoužitelná.
|
||||
`;
|
||||
|
||||
describe("useGitHubImportAutoTranslate — background streaming", () => {
|
||||
it("returns immediately with no translations (the list never waits on it)", () => {
|
||||
autoTranslateImportIssues.mockReturnValue(new Promise(() => {})); // never resolves
|
||||
@@ -277,6 +301,22 @@ Regression: PR #2147 review. The signature delimiter was ambiguous because prose
|
||||
moving a `|` between title and body produced an unchanged signature and the panel kept serving the
|
||||
OLD translation. Length-prefixing makes the encoding injective.
|
||||
*/
|
||||
describe("useGitHubImportTranslation — manual offer", () => {
|
||||
it("renders the translate offer for a foreign issue-form body when auto-translate is off", () => {
|
||||
const { result } = renderHook(() => useGitHubImportTranslation({
|
||||
selectionKey: "issue:2306",
|
||||
title: "PWA na iOS bez tokenu",
|
||||
body: REPORTED_CZECH_ISSUE_FORM,
|
||||
dashboardLocale: "en",
|
||||
autoTranslateEnabled: false,
|
||||
}));
|
||||
|
||||
const view = render(result.current.controls);
|
||||
expect(view.getByTestId("github-import-translate-message").textContent).toContain("This content appears");
|
||||
expect(view.getByTestId("github-import-translate-action")).toHaveTextContent("Translate");
|
||||
});
|
||||
});
|
||||
|
||||
describe("hashImportItemsForKey", () => {
|
||||
it("distinguishes content that only differs in where a delimiter falls", () => {
|
||||
const a = [{ number: 1, title: "a|b", body: "c", state: "open" as const }];
|
||||
|
||||
@@ -70,6 +70,75 @@ See https://github.com/owner/repo/issues/1 for context about the users.
|
||||
});
|
||||
});
|
||||
|
||||
/* Realistic issue-form fixtures deliberately include English scaffolding around user prose. */
|
||||
const SPANISH_ISSUE_FORM = `
|
||||
<!-- Please complete each field before submitting. -->
|
||||
### Bug description
|
||||
**What happened?**
|
||||
### Expected behavior
|
||||
**What did you expect to happen?**
|
||||
### Environment
|
||||
**Which operating system and version are you using?**
|
||||
### Additional details
|
||||
**What other context would help us investigate?**
|
||||
El servidor devuelve un error cuando el usuario intenta guardar los cambios y la aplicación no responde. Por favor revise los registros porque este problema ocurre para todos los usuarios después de actualizar la configuración.
|
||||
|
||||
### Steps to reproduce
|
||||
**Steps**
|
||||
- [x] I searched existing issues
|
||||
- [ ] I can provide more details
|
||||
1. Abra la configuración y guarde los cambios para comprobar que el fallo aparece de nuevo cuando el sistema procesa la solicitud.
|
||||
|
||||
### Additional context
|
||||
**Logs**
|
||||
_No response_
|
||||
`;
|
||||
|
||||
const HANGUL_ISSUE_FORM = `
|
||||
<!-- Please complete each field before submitting. -->
|
||||
### Bug description
|
||||
**What happened?**
|
||||
### Expected behavior
|
||||
**What did you expect to happen?**
|
||||
### Environment
|
||||
**Which operating system and version are you using?**
|
||||
### Additional details
|
||||
**What other context would help us investigate?**
|
||||
대시보드에서 설정을 저장하면 오류가 발생하고 사용자가 변경한 내용을 확인할 수 없습니다. 이 문제는 모든 프로젝트에서 반복되며 화면을 새로 고쳐도 계속됩니다.
|
||||
|
||||
### Steps to reproduce
|
||||
**Steps**
|
||||
- [x] I searched existing issues
|
||||
- [ ] I can provide more details
|
||||
|
||||
### Additional context
|
||||
**Logs**
|
||||
_No response_
|
||||
`;
|
||||
|
||||
const REPORTED_CZECH_ISSUE_FORM = `
|
||||
# PWA na iOS: studený start bez tokenu skončí ve smyčce „Can't reach Fusion Backend" — dialog pro vložení tokenu se nikdy nezobrazí
|
||||
|
||||
**GitHub issue:** Runfusion/Fusion#TBD
|
||||
**Verze Fusion:** 0.72.0 (zdrojový checkout)
|
||||
**Oblast:** Dashboard / autentizace (PWA, vzdálený přístup přes tunel)
|
||||
**Závažnost:** Vysoká — aplikaci přidanou na plochu iPhonu nelze vůbec autorizovat.
|
||||
|
||||
## Shrnutí
|
||||
|
||||
Dashboard zpřístupněný přes Remote Access funguje v mobilním Safari správně. Token přiteče přes přihlašovací URL a uloží se do localStorage. Po přidání na plochu na iOS ale instalovaná webová aplikace startuje bez tokenu v URL, běží v izolovaném úložišti a místo dialogu pro vložení tokenu zobrazí jen chybovou stránku Unauthorized.
|
||||
|
||||
## Reprodukce
|
||||
|
||||
1. Spusťte dashboard s aktivní bearer-token autentizací a zpřístupněte ho přes tunel.
|
||||
2. Na iPhonu otevřete přihlašovací URL v Safari a potom aplikaci přidejte na plochu.
|
||||
3. Otevřete aplikaci z plochy: zobrazí se chyba Unauthorized a tlačítko Retry Connection nic nedělá.
|
||||
|
||||
## Očekávané chování
|
||||
|
||||
Nepřihlášený studený start nabídne vložení tokenu, aby aplikace nebyla trvale nepoužitelná.
|
||||
`;
|
||||
|
||||
describe("contentNeedsTranslation", () => {
|
||||
const french =
|
||||
"Cette issue décrit le problème avec l'aperçu d'importation et ce que nous devrions changer pour les utilisateurs qui ont du contenu dans une autre langue dans le tableau de bord.";
|
||||
@@ -100,6 +169,57 @@ describe("contentNeedsTranslation", () => {
|
||||
it("offers translation for Chinese content when dashboard is English", () => {
|
||||
expect(contentNeedsTranslation(chinese, "en").needed).toBe(true);
|
||||
});
|
||||
|
||||
it("detects foreign GitHub issue-form answers instead of English scaffolding", () => {
|
||||
expect(contentNeedsTranslation(SPANISH_ISSUE_FORM, "en").needed).toBe(true);
|
||||
expect(contentNeedsTranslation(HANGUL_ISSUE_FORM, "en").needed).toBe(true);
|
||||
});
|
||||
|
||||
it("recognizes the reported Czech issue-form body as unsupported foreign Latin prose", () => {
|
||||
const result = contentNeedsTranslation(REPORTED_CZECH_ISSUE_FORM, "en");
|
||||
expect(result.needed).toBe(true);
|
||||
expect(result.detected).toMatchObject({ locale: "unknown", family: "latin", confidence: "high" });
|
||||
});
|
||||
|
||||
it("does not offer translation for English issue-form answers", () => {
|
||||
const englishForm = SPANISH_ISSUE_FORM.replace(
|
||||
/El servidor[\s\S]*?solicitud\./,
|
||||
"The server returns an error when a user saves settings, and the application stops responding for every project after configuration changes.",
|
||||
).replace(
|
||||
/Abra la configuración[\s\S]*?solicitud\./,
|
||||
"Open settings and save the changes to confirm that the problem happens again when the system processes the request.",
|
||||
);
|
||||
expect(contentNeedsTranslation(englishForm, "en").needed).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps scaffold-only issue forms unknown", () => {
|
||||
const scaffoldOnly = `
|
||||
### Bug description
|
||||
**What happened?**
|
||||
_No response_
|
||||
- [x] I searched existing issues
|
||||
<!-- Form guidance -->`;
|
||||
const result = contentNeedsTranslation(scaffoldOnly, "en");
|
||||
expect(result.needed).toBe(false);
|
||||
expect(result.detected.locale).toBe("unknown");
|
||||
});
|
||||
|
||||
it("preserves inline bold, list prose, and quoted foreign content", () => {
|
||||
const ordinaryProse = `**palabra importante**
|
||||
- falla al guardar
|
||||
> Esta explicación citada confirma que el servidor devuelve errores para todos los proyectos y los usuarios cuando intentan guardar cambios en la configuración.`;
|
||||
expect(contentNeedsTranslation("**palabra importante**\n- falla al guardar", "en").needed).toBe(false);
|
||||
expect(contentNeedsTranslation(ordinaryProse, "en").needed).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves meaningful headings and task-list prose outside issue forms", () => {
|
||||
const markdownReport = `### Informe del problema
|
||||
- [x] El servidor devuelve errores para todos los usuarios cuando guardan cambios en la configuración.
|
||||
### Pasos realizados
|
||||
- [x] Abra el panel, cambie una opción y guarde para confirmar que el fallo continúa.`;
|
||||
|
||||
expect(contentNeedsTranslation(markdownReport, "en").needed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("localeDisplayName", () => {
|
||||
|
||||
@@ -86,6 +86,51 @@ const SPANISH_BODY =
|
||||
const ENGLISH_BODY =
|
||||
"The server returns an error when the user tries to save the changes to the configuration. The operation cannot be completed because the system does not respond. Please review the server logs for more information about this problem.";
|
||||
|
||||
const REPORTED_CZECH_ISSUE_FORM = `
|
||||
# PWA na iOS: studený start bez tokenu skončí ve smyčce „Can't reach Fusion Backend" — dialog pro vložení tokenu se nikdy nezobrazí
|
||||
|
||||
**GitHub issue:** Runfusion/Fusion#TBD
|
||||
**Verze Fusion:** 0.72.0 (zdrojový checkout)
|
||||
**Oblast:** Dashboard / autentizace (PWA, vzdálený přístup přes tunel)
|
||||
**Závažnost:** Vysoká — aplikaci přidanou na plochu iPhonu nelze vůbec autorizovat.
|
||||
|
||||
## Shrnutí
|
||||
|
||||
Dashboard zpřístupněný přes Remote Access funguje v mobilním Safari správně. Token přiteče přes přihlašovací URL a uloží se do localStorage. Po přidání na plochu na iOS ale instalovaná webová aplikace startuje bez tokenu v URL, běží v izolovaném úložišti a místo dialogu pro vložení tokenu zobrazí jen chybovou stránku Unauthorized.
|
||||
|
||||
## Reprodukce
|
||||
|
||||
1. Spusťte dashboard s aktivní bearer-token autentizací a zpřístupněte ho přes tunel.
|
||||
2. Na iPhonu otevřete přihlašovací URL v Safari a potom aplikaci přidejte na plochu.
|
||||
3. Otevřete aplikaci z plochy: zobrazí se chyba Unauthorized a tlačítko Retry Connection nic nedělá.
|
||||
|
||||
## Očekávané chování
|
||||
|
||||
Nepřihlášený studený start nabídne vložení tokenu, aby aplikace nebyla trvale nepoužitelná.
|
||||
`;
|
||||
|
||||
const GITLAB_HANGUL_ISSUE_FORM = `
|
||||
<!-- Please complete each field before submitting. -->
|
||||
### Bug description
|
||||
**What happened?**
|
||||
### Expected behavior
|
||||
**What did you expect to happen?**
|
||||
### Environment
|
||||
**Which operating system and version are you using?**
|
||||
### Additional details
|
||||
**What other context would help us investigate?**
|
||||
대시보드에서 설정을 저장하면 오류가 발생하고 사용자가 변경한 내용을 확인할 수 없습니다. 이 문제는 모든 프로젝트에서 반복되며 화면을 새로 고쳐도 계속됩니다.
|
||||
|
||||
### Steps to reproduce
|
||||
**Steps**
|
||||
- [x] I searched existing issues
|
||||
- [ ] I can provide more details
|
||||
|
||||
### Additional context
|
||||
**Logs**
|
||||
_No response_
|
||||
`;
|
||||
|
||||
beforeEach(() => {
|
||||
translateTextMock.mockReset();
|
||||
translateTextMock.mockResolvedValue({ title: "TRANSLATED", body: "TRANSLATED BODY" });
|
||||
@@ -143,6 +188,17 @@ describe("isTranslatable", () => {
|
||||
it("accepts foreign-language open content", () => {
|
||||
expect(isTranslatable({ number: 1, title: "Error del servidor", body: SPANISH_BODY, state: "open" }, "en")).toBe(true);
|
||||
});
|
||||
|
||||
it("selects foreign GitHub and GitLab issue-form bodies through the provider-agnostic seam", () => {
|
||||
const items = [
|
||||
{ number: 2306, title: "PWA na iOS bez tokenu", body: REPORTED_CZECH_ISSUE_FORM, state: "open" as const },
|
||||
{ number: 2307, title: "설정 저장 오류", body: GITLAB_HANGUL_ISSUE_FORM, state: "open" as const },
|
||||
];
|
||||
|
||||
expect(isTranslatable(items[0], "en")).toBe(true);
|
||||
expect(isTranslatable(items[1], "en")).toBe(true);
|
||||
expect(selectEligibleItems(items, "en").map((item) => item.number)).toEqual([2306, 2307]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hashSourceContent", () => {
|
||||
|
||||
Reference in New Issue
Block a user