Every 'being written in parallel' / 'ASSUMED SIGNATURE' TODO is gone: each referenced module now exists and each assumption was checked against it rather than the comment simply deleted. - logo/presets/retouch: renderer typed as the real Renderer contract instead of unknown / (...args: never[]). - director: the ModelRegistry assumption is VERIFIED against the installed @earendil-works types (find() on ModelRegistry, getModel() on ModelRuntime). - strings: the img2imgBug TODO had it backwards -- drawthings raises a graded message a static string cannot express, and this is the fallback. - poster: an Italian hint was being spliced into an English art prompt, which degrades these models. New director.refineArtPrompt() folds the note in via the LLM and falls back to the old splice on any failure, so it can only improve on the previous behaviour. scrubArtPrompt still runs either way. Remaining TODOs are one category only (Italian strings living in per-file tables rather than ui/strings.ts) and are accurate, not stale.
717 lines
30 KiB
TypeScript
717 lines
30 KiB
TypeScript
/**
|
|
* strings.ts — every word the user ever reads, in one place.
|
|
*
|
|
* RULE: no user-facing Italian text anywhere else in the codebase. Commands, tools,
|
|
* backends and renderers import `S` (and the helpers below) and never inline a phrase.
|
|
* Model-facing text (tool `description`, `promptSnippet`, art prompts) stays ENGLISH and
|
|
* does NOT live here — this file is only what a human reads on screen.
|
|
*
|
|
* Tone: warm, plain, non-technical. He hosts events, he does not write software.
|
|
* - "immagine", never "render" / "output" / "asset"
|
|
* - "carattere", never "font" / "tipografia"
|
|
* - "impaginazione", never "layout" / "template"
|
|
* - "stile salvato", never "preset"
|
|
* - say what happened and what to do next; never show a stack trace as the whole message
|
|
*/
|
|
|
|
import type { Block, Format, TemplateName } from "../design/spec.ts";
|
|
|
|
/** Block roles, as the brief form calls them. Keyed off the spec so it stays exhaustive. */
|
|
type Role = Block["role"];
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/** Values accepted by {@link fill}. */
|
|
export type FillVars = Record<string, string | number>;
|
|
|
|
/**
|
|
* Tiny interpolation: `fill("Ho salvato in {dove}", { dove: p })`.
|
|
* Unknown placeholders are left untouched (a visible `{x}` is a bug worth seeing).
|
|
*/
|
|
export function fill(template: string, vars: FillVars = {}): string {
|
|
return template.replace(/\{(\w+)\}/g, (whole, key: string) =>
|
|
Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : whole,
|
|
);
|
|
}
|
|
|
|
/** Italian list: ["a"] -> "a", ["a","b"] -> "a e b", ["a","b","c"] -> "a, b e c". */
|
|
export function list(items: readonly string[]): string {
|
|
if (items.length === 0) return "";
|
|
if (items.length === 1) return items[0]!;
|
|
return `${items.slice(0, -1).join(", ")} e ${items[items.length - 1]!}`;
|
|
}
|
|
|
|
/** Picks the right word for a count. Does not include the number. */
|
|
export function plural(n: number, one: string, many: string): string {
|
|
return n === 1 ? one : many;
|
|
}
|
|
|
|
/** A duration a human would say out loud, from milliseconds. */
|
|
export function duration(ms: number): string {
|
|
const s = Math.max(0, Math.round(ms / 1000));
|
|
if (s < 5) return "un istante";
|
|
if (s < 60) return `${s} secondi`;
|
|
const m = Math.round(s / 60);
|
|
return m === 1 ? "circa un minuto" : `circa ${m} minuti`;
|
|
}
|
|
|
|
/** A bulleted block, ready to print under a message. */
|
|
export function bullets(items: readonly string[]): string {
|
|
return items.map((i) => ` • ${i}`).join("\n");
|
|
}
|
|
|
|
/** What the user reads when something goes wrong: what happened, then what to do. */
|
|
export interface ErrorMessage {
|
|
/** One warm sentence: what happened. Never a stack trace. */
|
|
readonly message: string;
|
|
/** One sentence: what he can do about it. Omitted when there is nothing to do. */
|
|
readonly fix?: string;
|
|
/** Raw technical detail, for the log / the model — never the whole user message. */
|
|
readonly detail?: string;
|
|
}
|
|
|
|
/** Joins an {@link ErrorMessage} into the two lines shown on screen. */
|
|
export function errorText(e: ErrorMessage): string {
|
|
return e.fix ? `${e.message}\n${e.fix}` : e.message;
|
|
}
|
|
|
|
/**
|
|
* Builds a `ctx.ui.select()` menu from a labels record plus an order, and maps the
|
|
* chosen label back to its key. `select()` returns the label string and can return
|
|
* `undefined` on cancel — `pick()` passes that straight through.
|
|
*/
|
|
export function menu<K extends string>(labels: Record<K, string>, order: readonly K[]) {
|
|
return {
|
|
/** Fresh mutable array — `ctx.ui.select()` wants `string[]`. */
|
|
options: order.map((k) => labels[k]),
|
|
pick(chosen: string | undefined): K | undefined {
|
|
if (chosen === undefined) return undefined;
|
|
return order.find((k) => labels[k] === chosen);
|
|
},
|
|
};
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Label tables typed against the fixed contracts (exhaustive by construction)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const ROLE_LABELS: Record<Role, string> = {
|
|
title: "Titolo",
|
|
subtitle: "Sottotitolo",
|
|
date: "Data e ora",
|
|
venue: "Dove",
|
|
details: "Dettagli",
|
|
price: "Prezzo",
|
|
footer: "In fondo",
|
|
};
|
|
|
|
const ROLE_PLACEHOLDERS: Record<Role, string> = {
|
|
title: "Concerto jazz al chiostro",
|
|
subtitle: "Trio Marini in concerto",
|
|
date: "sabato 12 settembre, ore 21",
|
|
venue: "Chiostro di San Domenico, Ravenna",
|
|
details: "Prenotazione consigliata",
|
|
price: "Ingresso libero",
|
|
footer: "Con il patrocinio del Comune",
|
|
};
|
|
|
|
const ROLE_HINTS: Record<Role, string> = {
|
|
title: "Il nome dell'evento, quello che si legge da lontano.",
|
|
subtitle: "Una riga in più, se serve. Puoi lasciarla vuota.",
|
|
date: "Scrivila come la diresti tu: la ricopio esattamente così.",
|
|
venue: "Nome del posto e città.",
|
|
details: "Cose utili: prenotazioni, età, cosa portare.",
|
|
price: "Per esempio «Ingresso libero» oppure «10 €».",
|
|
footer: "Patrocini, sponsor, sito, numero di telefono.",
|
|
};
|
|
|
|
const FORMAT_LABELS: Record<Format, string> = {
|
|
"a3-portrait": "Locandina A3 verticale (da stampare)",
|
|
"a4-portrait": "Foglio A4 verticale (da stampare)",
|
|
"ig-post": "Post per Instagram",
|
|
"ig-story": "Storia per Instagram",
|
|
"fb-cover": "Copertina di Facebook",
|
|
"yt-thumb": "Anteprima per YouTube",
|
|
};
|
|
|
|
const TEMPLATE_LABELS: Record<TemplateName, string> = {
|
|
"hero-bottom": "Immagine grande, scritte in basso",
|
|
banded: "Fascia colorata in mezzo",
|
|
framed: "Immagine dentro una cornice colorata",
|
|
"centred-stack": "Scritte al centro sopra l'immagine",
|
|
split: "Metà immagine, metà colore",
|
|
};
|
|
|
|
/** Output file names -> what that file actually is, for the closing summary. */
|
|
const FILE_LABELS: Record<string, string> = {
|
|
"poster-a3.pdf": "Locandina A3 pronta per la tipografia",
|
|
"poster-a3.png": "Locandina A3 come immagine, per WhatsApp",
|
|
"ig-post.png": "Post per Instagram",
|
|
"ig-story.png": "Storia per Instagram",
|
|
"fb-cover.png": "Copertina di Facebook",
|
|
"yt-thumb.png": "Anteprima per YouTube",
|
|
"art.png": "L'immagine di sfondo, senza scritte",
|
|
"caption.txt": "Il testo da copiare sui social",
|
|
"spec.json": "Le scelte di questo lavoro, per rifarlo uguale",
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// The strings
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export const S = {
|
|
app: {
|
|
name: "Locandine",
|
|
tagline: "Locandine, loghi e immagini per i tuoi eventi.",
|
|
/** Shown once at session start when everything is a posto. */
|
|
ready: "Tutto pronto. Scrivi /poster per iniziare.",
|
|
},
|
|
|
|
common: {
|
|
yes: "Sì",
|
|
no: "No",
|
|
ok: "Va bene",
|
|
cancel: "Annulla",
|
|
back: "Torna indietro",
|
|
skip: "Salta",
|
|
done: "Fatto",
|
|
none: "Nessuno",
|
|
empty: "(vuoto)",
|
|
/** Every dialog can be abandoned; say so once, gently. */
|
|
escHint: "Premi Esc per lasciare perdere.",
|
|
optional: "facoltativo",
|
|
cancelled: "Lasciamo perdere. Non ho toccato niente.",
|
|
somethingChanged: "Ho aggiornato quello che avevi chiesto.",
|
|
},
|
|
|
|
commands: {
|
|
poster: {
|
|
description: "Crea la locandina di un evento",
|
|
usage: "Puoi anche scrivere tutto di seguito: /poster concerto jazz al chiostro 12 settembre",
|
|
},
|
|
logo: {
|
|
description: "Crea un logo",
|
|
usage: "Per esempio: /logo per la mia band folk, tono caldo e artigianale",
|
|
},
|
|
social: {
|
|
description: "Crea le immagini per Instagram e Facebook",
|
|
usage: "Per esempio: /social serata karaoke venerdì al bar Centrale",
|
|
},
|
|
retouch: {
|
|
description: "Ritocca un'immagine che hai già",
|
|
usage: "Per esempio: /retouch togli lo sfondo alla foto del logo",
|
|
},
|
|
presets: {
|
|
description: "Gestisci i tuoi stili salvati",
|
|
usage: "Colori, carattere e logo che riusi ogni volta.",
|
|
},
|
|
},
|
|
|
|
brief: {
|
|
title: "Raccontami l'evento",
|
|
intro: "Riempi quello che sai. Quello che lasci vuoto semplicemente non comparirà.",
|
|
/** Free text typed after the command was understood and used to pre-fill. */
|
|
prefilled: "Ho letto quello che hai scritto e ho già riempito qualcosa. Controlla e correggi pure.",
|
|
prefilledNothing: "Non sono riuscito a ricavarci molto: riempi tu i campi qui sotto.",
|
|
/** The catch-all field every form must have. */
|
|
freeTextLabel: "Altro / contesto",
|
|
freeTextPlaceholder: "Il tono, il pubblico, cosa NON deve esserci, qualsiasi cosa ti venga in mente",
|
|
freeTextHint: "Scrivi come parli: al resto penso io.",
|
|
labels: ROLE_LABELS,
|
|
placeholders: ROLE_PLACEHOLDERS,
|
|
hints: ROLE_HINTS,
|
|
submit: "Vai",
|
|
/** Shown when he confirms a form with no title at all. */
|
|
needTitle: "Mi serve almeno il titolo dell'evento.",
|
|
confirmTitle: "Ho capito così",
|
|
confirmQuestion: "Procedo?",
|
|
},
|
|
|
|
formats: {
|
|
title: "Che cosa ti serve?",
|
|
labels: FORMAT_LABELS,
|
|
order: [
|
|
"a3-portrait", "a4-portrait", "ig-post", "ig-story", "fb-cover", "yt-thumb",
|
|
] as const satisfies readonly Format[],
|
|
/** The default bundle: everything, always. He asked for finished files. */
|
|
allNote: "Preparo la locandina da stampare e tutte le versioni per i social.",
|
|
},
|
|
|
|
templates: {
|
|
title: "Come disponiamo le scritte?",
|
|
labels: TEMPLATE_LABELS,
|
|
order: [
|
|
"hero-bottom", "banded", "framed", "centred-stack", "split",
|
|
] as const satisfies readonly TemplateName[],
|
|
keepCurrent: "Lascia com'è",
|
|
},
|
|
|
|
presets: {
|
|
title: "Da dove partiamo?",
|
|
fresh: "Parti da zero",
|
|
freshHint: "Scelgo io colori e carattere adatti all'evento.",
|
|
use: "Usa lo stile «{nome}»",
|
|
useHint: "Riprende colori, carattere e logo che hai salvato.",
|
|
none: "Non hai ancora salvato nessuno stile.",
|
|
manageTitle: "I tuoi stili salvati",
|
|
createLabel: "Salva questo stile",
|
|
nameQuestion: "Come lo chiamiamo?",
|
|
namePlaceholder: "La mia band",
|
|
saved: "Ho salvato lo stile «{nome}».",
|
|
overwriteQuestion: "Esiste già uno stile che si chiama «{nome}». Lo sostituisco?",
|
|
deleteQuestion: "Cancello lo stile «{nome}»?",
|
|
deleted: "Ho cancellato lo stile «{nome}».",
|
|
applied: "Sto usando lo stile «{nome}».",
|
|
defaultSet: "D'ora in poi parto da «{nome}».",
|
|
|
|
// --- /presets: il gestore degli stili salvati ---
|
|
intro: "Uno stile salvato è l'insieme di colori, carattere e logo che riusi ogni volta.",
|
|
actions: {
|
|
list: "Guarda quelli che hai",
|
|
create: "Crea uno stile nuovo",
|
|
edit: "Modifica uno stile",
|
|
remove: "Cancella uno stile",
|
|
setDefault: "Scegli da quale partire",
|
|
close: "Chiudi",
|
|
},
|
|
order: ["list", "create", "edit", "remove", "setDefault", "close"] as const,
|
|
pick: "Quale stile?",
|
|
listHeader: "I tuoi stili salvati:",
|
|
listEmpty: "Non hai ancora salvato nessuno stile.",
|
|
listHint: "Scrivi /presets e scegli «Crea uno stile nuovo».",
|
|
isDefault: "— parto da questo",
|
|
detailTitle: "Lo stile «{nome}»",
|
|
fieldLabels: {
|
|
palette: "Colori",
|
|
fonts: "Carattere",
|
|
logo: "Logo",
|
|
tone: "Descrizione",
|
|
},
|
|
moodQuestion: "Che aria deve avere?",
|
|
moodKeep: "Lascia com'è",
|
|
fontsQuestion: "Che carattere usiamo?",
|
|
fontsKeep: "Quello che viene con l'aria che hai scelto",
|
|
coloursQuestion: "Vuoi mettere i colori tuoi?",
|
|
colourAsk: "{campo} — scrivilo così: #B4121F",
|
|
colourKeep: "Lascia vuoto per tenere quello che c'è.",
|
|
colourFields: {
|
|
bg: "Sfondo",
|
|
ink: "Testo",
|
|
accent: "Colore in evidenza",
|
|
scrim: "Velo dietro le scritte",
|
|
},
|
|
colourInvalid: "«{valore}» non è un colore che so leggere: ci vuole qualcosa come #1A2B3C.",
|
|
colourAdjusted: "Ho ritoccato qualche colore perché le scritte restassero leggibili:",
|
|
logoQuestion: "Dov'è il file del logo?",
|
|
logoPlaceholder: "~/Immagini/logo-band.png",
|
|
logoHint: "Trascina qui il file. Lascia vuoto se non hai un logo.",
|
|
logoNone: "Nessun logo",
|
|
logoNotAFile: "«{percorso}» non è un file: controlla il percorso.",
|
|
logoBadType: "Il logo dev'essere un PNG, un JPG o un SVG.",
|
|
toneQuestion: "Come lo descriveresti in due parole?",
|
|
tonePlaceholder: "caldo, artigianale, un po' anni Settanta",
|
|
toneHint: "Mi serve per dare il tono alle immagini. Puoi lasciarlo vuoto.",
|
|
summaryTitle: "Ecco com'è «{nome}»",
|
|
confirmSave: "Lo salvo?",
|
|
defaultQuestion: "Vuoi che d'ora in poi parta da questo?",
|
|
defaultNone: "Nessuno: ogni volta parto da zero.",
|
|
defaultCleared: "Va bene: d'ora in poi parto da zero ogni volta.",
|
|
editWhat: "Cosa cambio di «{nome}»?",
|
|
editFields: {
|
|
label: "Il nome",
|
|
mood: "L'aria: colori e carattere insieme",
|
|
colours: "Solo i colori",
|
|
fonts: "Solo il carattere",
|
|
logo: "Il logo",
|
|
tone: "La descrizione",
|
|
done: "Ho finito",
|
|
},
|
|
editOrder: ["label", "mood", "colours", "fonts", "logo", "tone", "done"] as const,
|
|
updated: "Ho aggiornato lo stile «{nome}».",
|
|
nameMissing: "Mi serve un nome, altrimenti non so come richiamarlo.",
|
|
nameBad: "Quel nome non va bene: usa lettere e numeri, per esempio «La mia band».",
|
|
nothingChanged: "Non ho cambiato niente.",
|
|
savedWhere: "L'ho scritto nelle tue impostazioni: {dove}",
|
|
needsTerminal: "Per creare o modificare uno stile mi serve il terminale: apri pi e scrivi /presets.",
|
|
},
|
|
|
|
progress: {
|
|
thinking: "Sto immaginando la locandina…",
|
|
generating: "Genero l'immagine…",
|
|
typesetting: "Compongo il testo…",
|
|
pdf: "Preparo il PDF per la stampa…",
|
|
warmup: "Preparo il motore delle immagini…",
|
|
loadingModel: "Carico il modello…",
|
|
draft: "Faccio una prova veloce…",
|
|
finalArt: "Rifaccio l'immagine in alta definizione. Ci vuole qualche minuto…",
|
|
cropping: "Ritaglio le versioni per i social…",
|
|
exporting: "Salvo i file…",
|
|
checkingPdf: "Controllo che il PDF sia stampabile…",
|
|
removingBackground: "Tolgo lo sfondo…",
|
|
upscaling: "Ingrandisco l'immagine…",
|
|
vectorizing: "Ricalco il logo con linee nitide…",
|
|
caption: "Scrivo il testo per i social…",
|
|
openingFolder: "Apro la cartella…",
|
|
/** For long steps: fill(S.progress.step, { n, tot, cosa }). */
|
|
step: "Passo {n} di {tot} — {cosa}",
|
|
elapsed: "Sono passati {tempo}.",
|
|
stillWorking: "Ci sto ancora lavorando. Non è bloccato.",
|
|
/** Status-bar line while a generation holds the machine. */
|
|
busyStatus: "Sto lavorando a «{titolo}»",
|
|
},
|
|
|
|
draft: {
|
|
ready: "Ecco una prova veloce.",
|
|
note: "È una bozza sgranata: serve solo a decidere l'aspetto. Il file buono lo faccio dopo.",
|
|
seedNote: "Se l'immagine ti piace, la rifaccio identica ma nitida.",
|
|
compare: "Prima / dopo",
|
|
},
|
|
|
|
refine: {
|
|
title: "Che ne dici?",
|
|
hint: "Puoi cambiare quello che vuoi quante volte vuoi.",
|
|
actions: {
|
|
art: "Rigenera l'immagine",
|
|
colours: "Cambia colori",
|
|
fonts: "Cambia carattere",
|
|
layout: "Cambia impaginazione",
|
|
text: "Modifica il testo",
|
|
accept: "Va bene così",
|
|
},
|
|
order: ["art", "colours", "fonts", "layout", "text", "accept"] as const,
|
|
/** The whole point of the pipeline: say plainly that these are instant. */
|
|
instant: "Questo lo cambio subito, senza rifare l'immagine.",
|
|
slow: "Questo vuol dire rifare l'immagine da capo: ci vogliono un paio di minuti.",
|
|
artQuestion: "Cosa cambio nell'immagine?",
|
|
artPlaceholder: "più luce, niente persone, di sera invece che di giorno",
|
|
coloursQuestion: "Che colori vuoi?",
|
|
coloursPlaceholder: "più caldi, sul rosso mattone, sfondo scuro",
|
|
fontsQuestion: "Che carattere preferisci?",
|
|
fontsHint: "Scegli dall'elenco, oppure dimmi che effetto vuoi: elegante, robusto, allegro.",
|
|
layoutQuestion: "Come vuoi disporre le scritte?",
|
|
textQuestion: "Cosa correggo nel testo?",
|
|
textHint: "Riscrivo esattamente quello che scrivi tu, accenti compresi.",
|
|
applied: "Fatto. Ecco com'è adesso.",
|
|
nothingToChange: "Non ho capito cosa cambiare: prova a dirmelo con altre parole.",
|
|
},
|
|
|
|
confirm: {
|
|
overwrite: {
|
|
title: "Cartella già esistente",
|
|
message: "In «{cartella}» c'è già un lavoro con questo nome. Lo sostituisco?",
|
|
},
|
|
regenerateArt: {
|
|
title: "Rifaccio l'immagine",
|
|
message: "Devo ridisegnare tutto da capo: ci vogliono un paio di minuti. Procedo?",
|
|
},
|
|
finalRender: {
|
|
title: "Versione definitiva",
|
|
message: "Preparo i file buoni in alta definizione. È il passaggio più lento. Procedo?",
|
|
},
|
|
discard: {
|
|
title: "Butto via il lavoro",
|
|
message: "Perdi la bozza che abbiamo fatto finora. Sei sicuro?",
|
|
},
|
|
quitWhileWorking: {
|
|
title: "Sto ancora lavorando",
|
|
message: "Se esci adesso l'immagine in corso va persa. Esco lo stesso?",
|
|
},
|
|
/** Captions are ALWAYS asked for, never generated silently. */
|
|
wantCaption: {
|
|
title: "Testo per i social",
|
|
message: "Vuoi che scriva anche il testo del post con qualche hashtag?",
|
|
},
|
|
openFolder: {
|
|
title: "Cartella",
|
|
message: "Apro la cartella con i file?",
|
|
},
|
|
usePhoto: {
|
|
title: "Foto",
|
|
message: "Uso la foto «{file}» al posto di un'immagine disegnata?",
|
|
},
|
|
},
|
|
|
|
caption: {
|
|
ready: "Ecco un testo che puoi copiare:",
|
|
savedTo: "L'ho messo in caption.txt.",
|
|
declined: "Va bene, niente testo.",
|
|
/** He writes his own copy sometimes; never touch it. */
|
|
keepingYours: "Il testo che hai scritto tu lo lascio esattamente com'è.",
|
|
hashtagsNote: "Gli hashtag sono un'ipotesi: cambiali pure.",
|
|
},
|
|
|
|
done: {
|
|
header: "Ho finito.",
|
|
folder: "Trovi tutto in {cartella}",
|
|
filesHeader: "Nella cartella ci sono:",
|
|
files: FILE_LABELS,
|
|
printNote: "Il PDF è già pronto per la tipografia: ha il margine di taglio e le scritte al posto giusto.",
|
|
printNoCropMarks: "Non ci sono i segni di taglio, perché le tipografie online li rifiutano.",
|
|
reopenNote: "Per rifare questa locandina cambiando due parole, riapri la cartella e chiedimelo.",
|
|
elapsed: "Ci ho messo {tempo}.",
|
|
},
|
|
|
|
warnings: {
|
|
titleTooLong: "Il titolo è lungo: l'ho rimpicciolito per farlo stare. Se preferisci, accorcialo.",
|
|
blockDropped: "Non c'era spazio per «{campo}», così l'ho tolto. Dimmi se lo rivuoi.",
|
|
contrastFixed: "Le scritte non si leggevano bene sull'immagine: ho schiarito il testo.",
|
|
scrimAdded: "Ho messo un velo dietro le scritte, altrimenti sparivano nell'immagine.",
|
|
accentLowContrast: "Il colore in evidenza si vede poco su questo sfondo.",
|
|
photoLowRes: "La foto è piccola per una stampa A3: stampata si vedranno i pixel.",
|
|
/** Same problem, but we measured it: fill(..., { dpi }). */
|
|
photoLowResPrint:
|
|
"La foto è troppo piccola per la stampa: verrà a circa {dpi} dpi invece dei 300 consigliati, " +
|
|
"quindi da vicino si vedranno i pixel. Se ne hai una più grande, usa quella.",
|
|
/** The enlargement failed and the file is going to a printer: fill(..., { dpi }). */
|
|
upscaleFailedPrint:
|
|
"Non sono riuscito a ingrandire l'immagine: stampata verrà a circa {dpi} dpi invece dei 300 " +
|
|
"consigliati, quindi da vicino si vedranno i pixel. I file ci sono lo stesso.",
|
|
/** The enlargement failed on a screen format, where dpi means nothing. */
|
|
upscaleFailed:
|
|
"Non sono riuscito a ingrandire l'immagine: i file ci sono lo stesso, ma sono meno definiti del previsto.",
|
|
/** Not a bad day — a broken install. Never let this read like a transient failure. */
|
|
upscaleMissingModel:
|
|
"Manca il modello per ingrandire le immagini, così resto alla definizione di partenza. " +
|
|
"È un problema di configurazione: scrivi /doctor per vedere cosa manca.",
|
|
/** fill(..., { dpi }) */
|
|
upscaleMissingModelPrint:
|
|
"Manca il modello per ingrandire le immagini: stampata verrà a circa {dpi} dpi invece dei 300 " +
|
|
"consigliati. È un problema di configurazione, non della locandina: scrivi /doctor per vedere cosa manca.",
|
|
photoCropped: "La foto verrà ritagliata al centro per entrare nel formato.",
|
|
fontFallback: "Il carattere «{carattere}» non c'era, ne ho usato uno simile.",
|
|
slowMachine: "Sta andando più piano del solito. Meglio non aprire altri programmi pesanti.",
|
|
draftOnly: "Questa è ancora la bozza: i file buoni non li ho fatti.",
|
|
},
|
|
|
|
errors: {
|
|
modelsDiskMissing: (path: string): ErrorMessage => ({
|
|
message: "Non trovo il disco con i modelli: non è collegato.",
|
|
fix: fill("Collega il disco e riprova. Lo cerco in {dove}.", { dove: path }),
|
|
detail: path,
|
|
}),
|
|
modelMissing: (file: string, path: string): ErrorMessage => ({
|
|
message: fill("Manca il modello «{file}»: senza quello non so disegnare.", { file }),
|
|
fix: fill("Dovrebbe stare in {dove}. Scarica il file e rimettilo lì.", { dove: path }),
|
|
detail: `${path}/${file}`,
|
|
}),
|
|
drawThingsMissing: {
|
|
message: "Non trovo il programma che disegna le immagini.",
|
|
fix: "Installa Draw Things e il suo comando da terminale, poi scrivi /doctor per ricontrollare.",
|
|
} as ErrorMessage,
|
|
typstMissing: {
|
|
message: "Non trovo il programma che compone le scritte, quindi non posso preparare i file.",
|
|
fix: "Installa Typst (brew install typst), poi scrivi /doctor per ricontrollare.",
|
|
} as ErrorMessage,
|
|
serverStartFailed: (detail?: string): ErrorMessage => ({
|
|
message: "Il motore delle immagini non è partito.",
|
|
fix: "Riprova fra un momento. Se insiste, chiudi e riapri Draw Things.",
|
|
detail,
|
|
}),
|
|
serverNotReady: {
|
|
message: "Il motore delle immagini non risponde.",
|
|
fix: "Aspetto ancora un po' se riprovi; altrimenti riavvia Draw Things.",
|
|
} as ErrorMessage,
|
|
outOfMemory: {
|
|
message: "La memoria del computer è finita mentre disegnavo.",
|
|
fix: "Chiudi gli altri programmi e riprova. Posso anche fare l'immagine più piccola.",
|
|
} as ErrorMessage,
|
|
busy: {
|
|
message: "Sto già disegnando un'altra immagine.",
|
|
fix: "Aspetta che finisca: due immagini insieme bloccherebbero il computer.",
|
|
} as ErrorMessage,
|
|
/**
|
|
* Draw Things issue #121: partire da un'immagine esistente (img2img / edit) è
|
|
* inaffidabile su 16GB e il comando ritaglia da solo al centro.
|
|
* This is the FALLBACK wording. drawthings.ts raises its own graded message — it can
|
|
* distinguish a confirmed crash from a suspected one, which a static string cannot —
|
|
* and retouch.ts falls back to this only when a BackendError carries no Italian.
|
|
*/
|
|
img2imgBug: {
|
|
message: "Partire da un'immagine che hai già, su questo computer, non funziona in modo affidabile: è un difetto noto del programma che disegna.",
|
|
fix: "Facciamo un'immagine nuova partendo dalla descrizione: viene meglio e non rischiamo.",
|
|
detail: "draw-things-cli img2img (upstream issue #121)",
|
|
} as ErrorMessage,
|
|
generationFailed: (detail?: string): ErrorMessage => ({
|
|
message: "Non sono riuscito a disegnare l'immagine.",
|
|
fix: "Riprova: quasi sempre alla seconda va. Se continua, scrivi /doctor.",
|
|
detail,
|
|
}),
|
|
cancelled: {
|
|
message: "Ho annullato. Non ho salvato niente.",
|
|
} as ErrorMessage,
|
|
timeout: (minutes: number): ErrorMessage => ({
|
|
message: fill("Ci stava mettendo più di {n} {minuti}, così ho fermato tutto.", {
|
|
n: minutes,
|
|
minuti: plural(minutes, "minuto", "minuti"),
|
|
}),
|
|
fix: "Riprova, magari con un'immagine più piccola.",
|
|
}),
|
|
photoMissing: (path: string): ErrorMessage => ({
|
|
message: "Non trovo la foto che mi hai indicato.",
|
|
fix: "Controlla il percorso, oppure trascinala qui dentro.",
|
|
detail: path,
|
|
}),
|
|
logoMissing: (path: string): ErrorMessage => ({
|
|
message: "Non trovo il file del logo.",
|
|
fix: "Controlla dove l'hai messo, poi aggiorna il tuo stile salvato.",
|
|
detail: path,
|
|
}),
|
|
fontMissing: (family: string): ErrorMessage => ({
|
|
message: fill("Il carattere «{carattere}» non è installato.", { carattere: family }),
|
|
fix: "Ne scelgo un altro simile, oppure dimmi tu quale preferisci.",
|
|
detail: family,
|
|
}),
|
|
directorUnavailable: {
|
|
message: "Non riesco a parlare con l'assistente che decide lo stile.",
|
|
fix: "Controlla la connessione, poi riprova.",
|
|
} as ErrorMessage,
|
|
directorFailed: (detail?: string): ErrorMessage => ({
|
|
message: "Non sono riuscito a decidere come impostare la locandina.",
|
|
fix: "Riprova, magari raccontandomi un po' di più sull'evento.",
|
|
detail,
|
|
}),
|
|
briefEmpty: {
|
|
message: "Non ho capito di che evento si tratta.",
|
|
fix: "Bastano due parole: che cos'è, quando e dove.",
|
|
} as ErrorMessage,
|
|
renderFailed: (detail?: string): ErrorMessage => ({
|
|
message: "Qualcosa è andato storto mentre componevo le scritte.",
|
|
fix: "Riprova. Se succede ancora, prova a togliere i caratteri strani dal testo.",
|
|
detail,
|
|
}),
|
|
pdfFailed: (detail?: string): ErrorMessage => ({
|
|
message: "Non sono riuscito a preparare il PDF per la stampa.",
|
|
fix: "Le immagini per i social però ci sono. Riprova per il PDF.",
|
|
detail,
|
|
}),
|
|
preflightFailed: (problems: readonly string[]): ErrorMessage => ({
|
|
message: "Il PDF ha qualche problema per la tipografia:",
|
|
fix: bullets(problems),
|
|
detail: problems.join("; "),
|
|
}),
|
|
exportFailed: (what: string): ErrorMessage => ({
|
|
message: fill("Non sono riuscito a salvare «{cosa}».", { cosa: what }),
|
|
fix: "Gli altri file ci sono. Riprova solo per questo.",
|
|
detail: what,
|
|
}),
|
|
writeFailed: (path: string, detail?: string): ErrorMessage => ({
|
|
message: "Non riesco a scrivere nella cartella dei lavori.",
|
|
fix: fill("Controlla di poter scrivere in {dove}.", { dove: path }),
|
|
detail,
|
|
}),
|
|
diskFull: (path: string): ErrorMessage => ({
|
|
message: "Lo spazio sul disco è finito.",
|
|
fix: fill("Libera un po' di posto in {dove} e riprova.", { dove: path }),
|
|
detail: path,
|
|
}),
|
|
openFolderFailed: (path: string): ErrorMessage => ({
|
|
message: "Non sono riuscito ad aprire la cartella.",
|
|
fix: fill("La trovi qui: {dove}", { dove: path }),
|
|
detail: path,
|
|
}),
|
|
backgroundRemovalFailed: {
|
|
message: "Non sono riuscito a togliere lo sfondo.",
|
|
fix: "Funziona meglio con un soggetto ben staccato dallo sfondo.",
|
|
} as ErrorMessage,
|
|
upscaleFailed: {
|
|
message: "Non sono riuscito a ingrandire l'immagine.",
|
|
fix: "Tengo quella che c'è: per i social va benissimo lo stesso.",
|
|
} as ErrorMessage,
|
|
vectorizeFailed: {
|
|
message: "Non sono riuscito a ricalcare il logo con linee nitide.",
|
|
fix: "Ti lascio la versione a pixel, che per lo schermo va bene.",
|
|
} as ErrorMessage,
|
|
presetUnknown: (name: string): ErrorMessage => ({
|
|
message: fill("Non ho nessuno stile salvato che si chiami «{nome}».", { nome: name }),
|
|
fix: "Scrivi /presets per vedere quelli che hai.",
|
|
detail: name,
|
|
}),
|
|
configWriteFailed: (path: string, detail?: string): ErrorMessage => ({
|
|
message: "Non riesco a salvare le tue impostazioni.",
|
|
fix: fill("Controlla di poter scrivere in {dove}.", { dove: path }),
|
|
detail,
|
|
}),
|
|
/** Refusing to WRITE over a config we cannot parse — never says "vado avanti". */
|
|
configUnreadable: (path: string, detail?: string): ErrorMessage => ({
|
|
message: "Non riesco a leggere le tue impostazioni, così le ho lasciate stare: rischiavo di cancellartele.",
|
|
fix: fill("Il file è {dove}: sistemalo, oppure spostalo altrove e ricomincio da capo.", { dove: path }),
|
|
detail,
|
|
}),
|
|
configBroken: (path: string, detail?: string): ErrorMessage => ({
|
|
message: "Le tue impostazioni non si leggono, così vado avanti con quelle standard.",
|
|
fix: fill("Se vuoi sistemarle, il file è {dove}.", { dove: path }),
|
|
detail,
|
|
}),
|
|
unknown: (detail?: string): ErrorMessage => ({
|
|
message: "È successo qualcosa che non avevo previsto.",
|
|
fix: "Riprova. Se si ripete, scrivi /doctor e mandami cosa dice.",
|
|
detail,
|
|
}),
|
|
},
|
|
|
|
/**
|
|
* Health check. doctor.ts reports one line per check using these labels plus the
|
|
* `ok` / `ko` markers below.
|
|
*/
|
|
doctor: {
|
|
title: "Controllo generale",
|
|
running: "Controllo che ci sia tutto…",
|
|
allGood: "Tutto a posto: puoi lavorare.",
|
|
someProblems: "Qualcosa non va:",
|
|
ok: "a posto",
|
|
ko: "da sistemare",
|
|
checks: {
|
|
config: "Le tue impostazioni",
|
|
modelsDisk: "Il disco con i modelli",
|
|
models: "I modelli per disegnare",
|
|
drawThings: "Il programma che disegna",
|
|
server: "Il motore delle immagini",
|
|
typst: "Il programma che compone le scritte",
|
|
fonts: "I caratteri",
|
|
outputDir: "La cartella dei lavori",
|
|
diskSpace: "Lo spazio sul disco",
|
|
memory: "La memoria disponibile",
|
|
},
|
|
hint: "Sistemato quello che manca, scrivi di nuovo /doctor.",
|
|
},
|
|
|
|
notify: {
|
|
finished: "Locandina pronta.",
|
|
finishedNamed: "«{titolo}» è pronta.",
|
|
failed: "Non sono riuscito a finire.",
|
|
cancelled: "Annullato.",
|
|
draftReady: "Bozza pronta: dimmi che ne pensi.",
|
|
},
|
|
|
|
/** Labels shown next to a running tool in the terminal. Descriptions stay English. */
|
|
toolLabels: {
|
|
brief: "Raccolgo le informazioni",
|
|
direct: "Decido lo stile",
|
|
generateArt: "Disegno l'immagine",
|
|
typeset: "Compongo il testo",
|
|
export: "Salvo i file",
|
|
retouch: "Ritocco l'immagine",
|
|
caption: "Scrivo il testo per i social",
|
|
doctor: "Controllo generale",
|
|
},
|
|
} as const;
|
|
|
|
/** The shape of the whole table, for anything that wants to type against it. */
|
|
export type Strings = typeof S;
|
|
|
|
/** Ready-made `ctx.ui.select()` menus for the fixed choice lists. */
|
|
export const menus = {
|
|
refine: () => menu(S.refine.actions, S.refine.order),
|
|
formats: () => menu(FORMAT_LABELS, S.formats.order),
|
|
templates: () => menu(TEMPLATE_LABELS, S.templates.order),
|
|
};
|
|
|
|
/** What to call an output file on screen; falls back to the bare name. */
|
|
export function fileLabel(name: string): string {
|
|
return FILE_LABELS[name] ?? name;
|
|
}
|
|
|
|
export default S;
|