fix(ui): collapse bug-report connection diagnostic for multi-printer setups

The "Report a Bug" panel scans every configured printer on open and shows
  connection problems inline. The first cut rendered a full ~6-row checklist
  per problem printer, stacked — on a large fleet that pushed the description
  box and Submit button below the fold in the max-w-md / max-h-80vh panel.

  The diagnostic section is now a compact summary: one "N of M printers have
  connection issues" line plus the affected printers as collapsed rows
  (healthy printers count toward M, render no detail). Each row expands on
  demand to that printer's full checklist via the shared Collapsible widget;
  a single problem auto-expands since that's the case where inline detail is
  wanted without a click. Panel height is now fixed regardless of fleet size.

  - BugReportBubble.tsx: diagnostic query pairs each result with the printer
    name (new DiagnosticEntry type); render block uses Collapsible rows
  - i18n: new bugReport.diagnosticSummary ({{problems}}/{{total}}) replaces
    static diagnosticHeading; diagnosticIntro reworded count-neutral — all 9
    locales
  - 2 new BugReportBubble tests (collapsed-then-expand; single auto-expand)
This commit is contained in:
maziggy 2026-05-22 12:48:21 +02:00
parent 4925b4c830
commit ed31b8f4a4
16 changed files with 257 additions and 152 deletions

View file

@ -12,6 +12,7 @@ All notable changes to Bambuddy will be documented in this file.
- **Connection Diagnostic — self-service triage for "printer won't connect / won't print"** — A triage review of recently-closed issues found roughly a third were user-side setup errors (printer not in LAN developer mode, blocked ports, Docker bridge networking, wrong access code, printer on a different subnet), each costing a multi-round-trip "enable debug logging → build a support bundle → upload it" exchange. A new diagnostic (`backend/app/services/printer_diagnostic.py`) runs those checks automatically: TCP reachability of MQTT 8883 / FTPS 990 / RTSPS 322, LAN developer mode, Docker network mode, printer/host subnet match, and MQTT credential class — each returning a pass / fail / warn / skip status with a localized plain-language fix. Exposed via `GET /printers/{id}/diagnostic` (saved printer) and `POST /printers/diagnostic` (pre-save Add-Printer flow), and surfaced as a one-click "Run diagnostic" from the printer card actions menu (plus a quick button on the card when a printer is offline), the Add-Printer dialog, and a new Connection Diagnostic section on the System page. The in-app bug reporter scans configured printers when the report form opens and always shows the result — a healthy confirmation when nothing's wrong, or the detected problem and its fix inline — so setup mistakes get self-resolved instead of becoming GitHub issues. The GitHub `config.yml` troubleshooting link was repointed from the wiki source repo to the rendered troubleshooting page. Backend service unit tests (15) and frontend modal tests (3) added; all diagnostic strings translated across the 8 locales. Backend ruff clean, frontend build clean, i18n parity green.
### Changed
- **Bug-report panel: connection diagnostic no longer overflows on multi-printer setups** — The "Report a Bug" panel scans every configured printer on open and surfaces connection problems inline so users can self-fix before filing. The first cut rendered a full ~6-row checklist for *each* problem printer stacked vertically; a user with many printers all reporting issues pushed the description box, screenshot uploader and Submit button far below the fold in the `max-w-md` / `max-h-[80vh]` panel. The diagnostic section is now a compact summary — one line ("N of M printers have connection issues") followed by the affected printers as collapsed rows (healthy printers count toward M but render no detail). Each row expands on demand to that printer's full checklist via the shared `Collapsible` widget; when exactly one printer has problems the row is auto-expanded since that's the case where inline detail is wanted with no extra click. The panel now stays a fixed ~3 lines plus one row per affected printer regardless of fleet size, keeping the report form reachable. Healthy-fleet confirmation line is unchanged. New `bugReport.diagnosticSummary` key (with `{{problems}}`/`{{total}}`) replaces the static `diagnosticHeading`; `diagnosticIntro` reworded to be printer-count-neutral and point at the expand affordance — both translated across all 9 locales. 2 new tests in `BugReportBubble.test.tsx` (multiple problems stay collapsed and expand on click; a single problem auto-expands); 11 tests green; frontend build clean; i18n parity holds at 4903 keys × 9 locales.
- **Color Catalog sync now identifies itself as Bambuddy to filamentcolors.xyz** — The FilamentColors.xyz sync client in `inventory.py` created its `httpx.AsyncClient` with no `User-Agent`, so it leaked httpx's default `python-httpx/x.y` string — the only outbound client that did (`bambu_cloud`, `makerworld`, `firmware_check` all send the honest `Bambuddy/1.0 (+https://github.com/maziggy/bambuddy)`). It now sends the same honest UA, consistent with the rest of the codebase. Surfaced while investigating #1456 (a Cloudflare `403` on the sync that turned out to be the reporter's network/IP reputation, not Bambuddy — the UA leak was a separate inconsistency found in passing, and this change does not by itself resolve a Cloudflare IP block).
- **Filament inventory: grouped rows now show group totals (#1368, requested by a user)** — With "Group similar" enabled, the collapsed group row showed the values of a single member (the first spool) — so a group of five 1 kg spools displayed "1000 g" instead of the 5 kg it actually held. The group header now aggregates across all members: the table view's Label, Net, Gross, Used and Remaining columns and the grid card's weight figure show group totals, while identity columns (Material, Brand, Colour) and the Cost/kg rate stay per-spool-correct. Per-spool-only fields with no meaningful total (dates, location, note, tag ID) keep showing the representative member's value; the expanded individual rows are unchanged. New `aggregateGroupSpool` helper in `frontend/src/utils/inventoryGrouping.ts` with 4 unit tests. Frontend-only — all data was already in the spool list. — Previous behaviour disabled the Slice button whenever the source 3MF's bound printer model didn't match the user's picked printer profile, on the theory that the slicer CLI "cannot re-slice a 3MF for a different printer" and would silently fall back to embedded settings to produce a wrong-printer file. Step 0 empirical test on 2026-05-20 disproved that: an 18-color H2D-bound `Trent900.3mf` sliced via the X1C bundle (`POST /slice` with `bundle=cb…X1C, printerName=# Bambu Lab X1 Carbon 0.4 nozzle`) produced 2.3 MB of genuinely X1C-compatible G-code in 1.8 s — `printer_model` overridden to `Bambu Lab X1 Carbon`, `printable_area` to 256×256 (X1C bed, not H2D's 350×320), `printable_height` 250 (vs 325), `bed_exclude_area` populated with X1C's 18×28 corner zone, `nozzle_diameter` single 0.4 (vs H2D's dual `0.4,0.4`), and the full X1C `machine_start_gcode` sequence baked in. The sidecar takes printer / process / first-N filament names from the picked bundle and only inherits embedded values for unused trailing slots — bed size, kinematics, start sequence all come from the target. **Behavioural change**: dropped `!printerMismatch` from the SliceModal `isReady` predicate so the Slice button stays enabled when models differ. The amber banner was first softened to an info message, then removed entirely — re-slicing across printers is now just a normal slice, the picker UI already shows which printer was picked, no second confirmation needed. **Dead-code removal (same drop)**: with no banner, the `source_printer_model` field on the `/library/files/{id}/plates` and `/archives/{id}/plates` responses had zero consumers; the `extract_source_printer_model_from_3mf` helper in `threemf_tools.py` (which opened the 3MF zip and read `Metadata/project_settings.config` on every plate request) had zero callers. Removed both response keys, both backend extractions, both `threemf_tools` imports, the helper itself, its 6 unit tests, the `source_printer_model` field from `frontend/src/types/plates.ts` (PlateMetadata + LibraryFilePlatesResponse), and 2 obsolete SliceModal tests that exercised the now-impossible matched-printer / legacy-archive paths. **i18n discipline cleanup (same drop, per [[feedback_no_followups]] + [[feedback_translate_dont_fallback]])**: every t() callsite in SliceModal.tsx had an inline English `defaultValue:` or positional-second-arg English fallback — 22 sites in total. With 8 locales shipped, those fallbacks are dead weight at best, and an actual i18n-violation when the key is missing because non-English users would silently see English. Audit found 3 keys (`slice.bundle`, `slice.bundleNone`, `slice.bundleAllRequired`) that had **no** corresponding entry in any locale file — they were being served from the inline English fallback exclusively, meaning every non-English user was already seeing those three labels in English. Added all 3 to all 8 locales with real translations, then stripped the English fallback from every t() call in SliceModal.tsx. The `slice.printerMismatch` key was removed from all 8 locales (banner is gone). **Why this matters**: a recurring pain point for users importing MakerWorld project files where the original creator's printer often differs from the user's; previously they had to round-trip through BambuStudio's "convert project" flow to re-export. Now Bambuddy re-slices in-place with no UI friction. **Tests**: the existing SliceModal "shows mismatch warning AND disables Slice" test was rewritten to assert "does not surface any cross-printer banner AND keeps Slice enabled when models differ" (regression guard against the gate being re-added); 2 obsolete tests deleted. 32 SliceModal tests green (was 34, -2 dead tests); 49 threemf_tools tests green (was 55, -6 helper tests); 24 plates-route tests green; frontend build clean; backend ruff clean; i18n parity check passes 4858 keys × 8 locales (net +2 vs pre-fix: +3 bundle keys, -1 printerMismatch).

View file

@ -34,6 +34,37 @@ function setupLoggingEndpoints() {
);
}
/** Mocks the printer list and per-printer diagnostic the form scans on open. */
function setupDiagnosticEndpoints(
printers: { id: number; name: string }[],
results: Record<number, 'ok' | 'problems'>
) {
server.use(
http.get('*/printers/', () =>
HttpResponse.json(
printers.map((p) => ({
id: p.id,
name: p.name,
serial_number: '00M09A000000000',
ip_address: `192.168.1.${20 + p.id}`,
is_active: true,
model: 'X1C',
nozzle_count: 1,
}))
)
),
http.get('*/printers/:id/diagnostic', ({ params }) => {
const overall = results[Number(params.id)] ?? 'ok';
return HttpResponse.json({
printer_id: Number(params.id),
ip_address: `192.168.1.${20 + Number(params.id)}`,
overall,
checks: [{ id: 'port_mqtt', status: overall === 'problems' ? 'fail' : 'pass', params: {} }],
});
})
);
}
describe('BugReportBubble', () => {
it('renders the floating bug button', () => {
render(<BugReportBubble />);
@ -207,4 +238,48 @@ describe('BugReportBubble', () => {
const details = document.querySelector('details');
expect(details).toBeInTheDocument();
});
it('lists affected printers as collapsed rows, not stacked checklists', async () => {
const user = userEvent.setup();
setupDiagnosticEndpoints(
[
{ id: 1, name: 'Printer Alpha' },
{ id: 2, name: 'Printer Beta' },
{ id: 3, name: 'Printer Gamma' },
],
{ 1: 'problems', 2: 'problems', 3: 'ok' }
);
render(<BugReportBubble />);
await user.click(screen.getByRole('button'));
// Summary counts problem printers against all scanned printers.
expect(
await screen.findByText('2 of 3 printers have connection issues')
).toBeInTheDocument();
// Affected printers are listed by name; the healthy one is not.
expect(screen.getByText('Printer Alpha')).toBeInTheDocument();
expect(screen.getByText('Printer Beta')).toBeInTheDocument();
expect(screen.queryByText('Printer Gamma')).not.toBeInTheDocument();
// With more than one problem the per-printer checklists stay collapsed.
expect(screen.queryByText(/Found problems that explain/)).not.toBeInTheDocument();
// Expanding a row reveals just that printer's checklist.
await user.click(screen.getByText('Printer Alpha'));
expect(await screen.findByText(/Found problems that explain/)).toBeInTheDocument();
});
it('auto-expands the checklist when only one printer has problems', async () => {
const user = userEvent.setup();
setupDiagnosticEndpoints([{ id: 1, name: 'Solo Printer' }], { 1: 'problems' });
render(<BugReportBubble />);
await user.click(screen.getByRole('button'));
expect(
await screen.findByText('1 of 1 printers have connection issues')
).toBeInTheDocument();
// Single problem → the checklist is expanded without a click.
expect(await screen.findByText(/Found problems that explain/)).toBeInTheDocument();
});
});

View file

@ -1,12 +1,17 @@
import { useState, useRef, useCallback, useEffect } from 'react';
import { Bug, X, Loader2, CheckCircle, AlertCircle, Trash2, Upload, Circle, CheckCircle2, Stethoscope } from 'lucide-react';
import { Bug, X, Loader2, CheckCircle, AlertCircle, AlertTriangle, Trash2, Upload, Circle, CheckCircle2, Stethoscope } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
import { api, bugReportApi, type PrinterDiagnosticResult } from '../api/client';
import { DiagnosticChecklist } from './ConnectionDiagnostic';
import { Collapsible } from './Collapsible';
type ViewState = 'form' | 'logging' | 'stopping' | 'submitting' | 'success' | 'error';
/** One scanned printer paired with its name the diagnostic result alone
* carries no name, and the bug-report panel lists affected printers by name. */
type DiagnosticEntry = { name: string; result: PrinterDiagnosticResult };
const MAX_DIMENSION = 1920;
const JPEG_QUALITY = 0.7;
const MAX_LOG_SECONDS = 300; // 5 minutes
@ -66,16 +71,19 @@ export function BugReportBubble() {
queryKey: ['bugReportDiagnostic'],
enabled: isOpen && viewState === 'form',
staleTime: 30_000,
queryFn: async (): Promise<PrinterDiagnosticResult[]> => {
queryFn: async (): Promise<DiagnosticEntry[]> => {
const printers = await api.getPrinters();
const results = await Promise.all(
printers.map((p) => api.diagnosePrinter(p.id).catch(() => null)),
const entries = await Promise.all(
printers.map(async (p) => {
const result = await api.diagnosePrinter(p.id).catch(() => null);
return result ? { name: p.name, result } : null;
}),
);
return results.filter((r): r is PrinterDiagnosticResult => r !== null);
return entries.filter((e): e is DiagnosticEntry => e !== null);
},
});
const diagnosticResults = diagnosticScan.data ?? [];
const diagnosticProblems = diagnosticResults.filter((r) => r.overall === 'problems');
const diagnosticEntries = diagnosticScan.data ?? [];
const diagnosticProblems = diagnosticEntries.filter((e) => e.result.overall === 'problems');
// Elapsed timer for logging phase — auto-stop at 5 minutes
useEffect(() => {
@ -232,9 +240,11 @@ export function BugReportBubble() {
<div className="p-4 space-y-4">
{viewState === 'form' && (
<>
{/* Connection diagnostic scanned on form-open. The result
is always shown: a problem panel when a printer has a
detected setup issue, otherwise a healthy confirmation. */}
{/* Connection diagnostic scanned on form-open. A healthy
fleet shows a single confirmation line. When printers
have problems, each is a collapsed row (auto-expanded
when only one) so the form stays reachable regardless
of how many printers are configured. */}
{diagnosticScan.isLoading && (
<div className="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
<Loader2 className="w-3.5 h-3.5 animate-spin" />
@ -247,20 +257,39 @@ export function BugReportBubble() {
<Stethoscope className="w-4 h-4 mt-0.5 flex-shrink-0 text-amber-600 dark:text-amber-400" />
<div>
<p className="text-sm font-medium text-amber-700 dark:text-amber-300">
{t('bugReport.diagnosticHeading')}
{t('bugReport.diagnosticSummary', {
problems: diagnosticProblems.length,
total: diagnosticEntries.length,
})}
</p>
<p className="text-xs text-amber-800 dark:text-amber-200 mt-0.5">
{t('bugReport.diagnosticIntro')}
</p>
</div>
</div>
{diagnosticProblems.map((result) => (
<DiagnosticChecklist key={result.printer_id ?? result.ip_address} result={result} />
))}
<div className="space-y-2">
{diagnosticProblems.map((entry) => (
<Collapsible
key={entry.result.printer_id ?? entry.result.ip_address}
defaultOpen={diagnosticProblems.length === 1}
className="rounded-lg bg-amber-100/60 dark:bg-amber-900/30 px-3 py-2"
summary={
<div className="flex items-center gap-2 min-w-0">
<AlertTriangle className="w-4 h-4 flex-shrink-0 text-amber-600 dark:text-amber-400" />
<span className="text-sm font-medium text-amber-800 dark:text-amber-200 truncate">
{entry.name}
</span>
</div>
}
>
<DiagnosticChecklist result={entry.result} />
</Collapsible>
))}
</div>
</div>
)}
{!diagnosticScan.isLoading &&
diagnosticResults.length > 0 &&
diagnosticEntries.length > 0 &&
diagnosticProblems.length === 0 && (
<div className="flex items-start gap-2 rounded-lg bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 p-3">
<CheckCircle className="w-4 h-4 mt-0.5 flex-shrink-0 text-green-600 dark:text-green-400" />

View file

@ -5553,8 +5553,8 @@ export default {
submitFailed: 'Fehlerbericht konnte nicht gesendet werden',
diagnosticChecking: 'Druckerverbindungen werden geprüft...',
diagnosticHealthy: 'Verbindungsprüfung bestanden — keine Probleme an Ihren Druckern gefunden.',
diagnosticHeading: 'Mögliches Konfigurationsproblem erkannt',
diagnosticIntro: 'Ein Drucker hat ein Verbindungsproblem, das die Ursache Ihres Problems sein könnte. Prüfen Sie die Lösung unten — sie zu beheben könnte das Problem ohne Fehlerbericht lösen. Sie können unten dennoch einen Bericht senden.',
diagnosticSummary: '{{problems}} von {{total}} Druckern haben Verbindungsprobleme',
diagnosticIntro: 'Einer oder mehrere Drucker haben ein Verbindungsproblem, das die Ursache Ihres Problems sein könnte. Klappen Sie einen Drucker unten auf, um die Lösung zu sehen — sie zu beheben könnte das Problem ohne Fehlerbericht lösen. Sie können unten dennoch einen Bericht senden.',
thankYou: 'Vielen Dank!',
submitted: 'Ihr Fehlerbericht wurde eingereicht.',
viewIssue: 'Issue ansehen',

View file

@ -5566,8 +5566,8 @@ export default {
submitFailed: 'Failed to submit bug report',
diagnosticChecking: 'Checking printer connections...',
diagnosticHealthy: 'Connection check passed — no problems found on your printers.',
diagnosticHeading: 'Possible setup issue detected',
diagnosticIntro: 'A printer has a connection problem that may be causing your issue. Check the fix below — resolving it could solve the problem without a bug report. You can still submit a report below.',
diagnosticSummary: '{{problems}} of {{total}} printers have connection issues',
diagnosticIntro: 'One or more printers have a connection problem that may be causing your issue. Expand a printer below to see the fix — resolving it could solve the problem without a bug report. You can still submit a report below.',
thankYou: 'Thank you!',
submitted: 'Your bug report has been submitted.',
viewIssue: 'View Issue',

View file

@ -5562,8 +5562,8 @@ export default {
submitFailed: 'Error al enviar el informe de error',
diagnosticChecking: 'Comprobando las conexiones de las impresoras...',
diagnosticHealthy: 'La comprobación de conexión se superó — no se encontraron problemas en sus impresoras.',
diagnosticHeading: 'Posible problema de configuración detectado',
diagnosticIntro: 'Una impresora tiene un problema de conexión que puede estar causando su problema. Compruebe la solución de abajo — resolverla podría solucionar el problema sin un informe de error. Aún puede enviar un informe a continuación.',
diagnosticSummary: '{{problems}} de {{total}} impresoras tienen problemas de conexión',
diagnosticIntro: 'Una o más impresoras tienen un problema de conexión que puede estar causando su problema. Despliegue una impresora a continuación para ver la solución — resolverla podría solucionar el problema sin un informe de error. Aún puede enviar un informe a continuación.',
thankYou: '¡Gracias!',
submitted: 'Su informe de error se ha enviado.',
viewIssue: 'Ver incidencia',

View file

@ -5543,8 +5543,8 @@ export default {
submitFailed: 'Échec de l\'envoi du rapport de bug',
diagnosticChecking: 'Vérification des connexions des imprimantes...',
diagnosticHealthy: 'Vérification de connexion réussie — aucun problème détecté sur vos imprimantes.',
diagnosticHeading: 'Problème de configuration possible détecté',
diagnosticIntro: 'Une imprimante a un problème de connexion qui pourrait être à l\'origine de votre problème. Consultez la solution ci-dessous — la résoudre pourrait régler le problème sans rapport de bug. Vous pouvez tout de même envoyer un rapport ci-dessous.',
diagnosticSummary: '{{problems}} imprimante(s) sur {{total}} ont des problèmes de connexion',
diagnosticIntro: 'Une ou plusieurs imprimantes ont un problème de connexion qui pourrait être à l\'origine de votre problème. Dépliez une imprimante ci-dessous pour voir la solution — la résoudre pourrait régler le problème sans rapport de bug. Vous pouvez tout de même envoyer un rapport ci-dessous.',
thankYou: 'Merci !',
submitted: 'Votre rapport de bug a été soumis.',
viewIssue: 'Voir l\'issue',

View file

@ -5542,8 +5542,8 @@ export default {
submitFailed: 'Impossibile inviare la segnalazione bug',
diagnosticChecking: 'Verifica delle connessioni delle stampanti...',
diagnosticHealthy: 'Verifica della connessione superata — nessun problema rilevato sulle tue stampanti.',
diagnosticHeading: 'Possibile problema di configurazione rilevato',
diagnosticIntro: 'Una stampante ha un problema di connessione che potrebbe essere la causa del tuo problema. Controlla la soluzione qui sotto — risolverla potrebbe sistemare il problema senza una segnalazione di bug. Puoi comunque inviare una segnalazione qui sotto.',
diagnosticSummary: '{{problems}} stampanti su {{total}} hanno problemi di connessione',
diagnosticIntro: 'Una o più stampanti hanno un problema di connessione che potrebbe essere la causa del tuo problema. Espandi una stampante qui sotto per vedere la soluzione — risolverla potrebbe sistemare il problema senza una segnalazione di bug. Puoi comunque inviare una segnalazione qui sotto.',
thankYou: 'Grazie!',
submitted: 'La tua segnalazione bug è stata inviata.',
viewIssue: 'Vedi issue',

View file

@ -5554,8 +5554,8 @@ export default {
submitFailed: 'バグレポートの送信に失敗しました',
diagnosticChecking: 'プリンター接続を確認中...',
diagnosticHealthy: '接続チェックに合格しました — プリンターに問題は見つかりませんでした。',
diagnosticHeading: '設定の問題の可能性を検出しました',
diagnosticIntro: 'あるプリンターに接続の問題があり、それが今回の問題の原因である可能性があります。下記の対処法を確認してください — それを解決すれば、バグ報告なしで問題が解決するかもしれません。下記から報告を送信することもできます。',
diagnosticSummary: '{{total}} 台中 {{problems}} 台のプリンターに接続の問題があります',
diagnosticIntro: '1 台以上のプリンターに接続の問題があり、それが今回の問題の原因である可能性があります。下記のプリンターを展開して対処法を確認してください — それを解決すれば、バグ報告なしで問題が解決するかもしれません。下記から報告を送信することもできます。',
thankYou: 'ありがとうございます!',
submitted: 'バグレポートが送信されました。',
viewIssue: 'Issueを表示',

View file

@ -5542,8 +5542,8 @@ export default {
submitFailed: 'Falha ao enviar relatório de bug',
diagnosticChecking: 'Verificando as conexões das impressoras...',
diagnosticHealthy: 'Verificação de conexão aprovada — nenhum problema encontrado nas suas impressoras.',
diagnosticHeading: 'Possível problema de configuração detectado',
diagnosticIntro: 'Uma impressora tem um problema de conexão que pode estar causando seu problema. Confira a solução abaixo — resolvê-la pode solucionar o problema sem um relatório de bug. Você ainda pode enviar um relatório abaixo.',
diagnosticSummary: '{{problems}} de {{total}} impressoras têm problemas de conexão',
diagnosticIntro: 'Uma ou mais impressoras têm um problema de conexão que pode estar causando seu problema. Expanda uma impressora abaixo para ver a solução — resolvê-la pode solucionar o problema sem um relatório de bug. Você ainda pode enviar um relatório abaixo.',
thankYou: 'Obrigado!',
submitted: 'Seu relatório de bug foi enviado.',
viewIssue: 'Ver issue',

View file

@ -5541,8 +5541,8 @@ export default {
submitFailed: '提交错误报告失败',
diagnosticChecking: '正在检查打印机连接...',
diagnosticHealthy: '连接检查通过 — 未在您的打印机上发现问题。',
diagnosticHeading: '检测到可能的设置问题',
diagnosticIntro: '某台打印机存在连接问题,可能正是您遇到问题的原因。请查看下方的解决方法 — 解决它也许无需提交错误报告即可解决问题。您仍然可以在下方提交报告。',
diagnosticSummary: '{{total}} 台打印机中有 {{problems}} 台存在连接问题',
diagnosticIntro: '一台或多台打印机存在连接问题,可能正是您遇到问题的原因。请展开下方的打印机查看解决方法 — 解决它也许无需提交错误报告即可解决问题。您仍然可以在下方提交报告。',
thankYou: '谢谢!',
submitted: '您的错误报告已提交。',
viewIssue: '查看Issue',

View file

@ -5541,8 +5541,8 @@ export default {
submitFailed: '提交錯誤報告失敗',
diagnosticChecking: '正在檢查印表機連線...',
diagnosticHealthy: '連線檢查通過 — 未在您的印表機上發現問題。',
diagnosticHeading: '偵測到可能的設定問題',
diagnosticIntro: '某台印表機存在連線問題,可能正是您遇到問題的原因。請查看下方的解決方法 — 解決它也許無需提交錯誤報告即可解決問題。您仍然可以在下方提交報告。',
diagnosticSummary: '{{total}} 台印表機中有 {{problems}} 台存在連線問題',
diagnosticIntro: '一台或多台印表機存在連線問題,可能正是您遇到問題的原因。請展開下方的印表機查看解決方法 — 解決它也許無需提交錯誤報告即可解決問題。您仍然可以在下方提交報告。',
thankYou: '謝謝!',
submitted: '您的錯誤報告已提交。',
viewIssue: '檢視 Issue',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -26,8 +26,8 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-DOnLEVBo.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Cqtgi1Io.css">
<script type="module" crossorigin src="/assets/index-CVVS0VHp.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DYdMf_Qm.css">
</head>
<body>
<div id="root"></div>