pgkiln 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/db/migrations/095_rest_catalogs.sql +59 -0
  2. package/db/migrations/096_developer_email.sql +15 -0
  3. package/docs/apex-feature-parity.md +12 -9
  4. package/docs/guide/03-builder.md +37 -1
  5. package/docs/guide/04-pages-and-regions.md +37 -4
  6. package/docs/guide/12-development.md +18 -1
  7. package/docs/guide/19-rest-data-sources.md +39 -1
  8. package/examples/hr/hr_59_gantt_editing.sql +21 -0
  9. package/examples/hr/hr_60_tree_charts.sql +46 -0
  10. package/package.json +1 -1
  11. package/public/app.css +44 -0
  12. package/public/app.js +126 -0
  13. package/public/builder.css +1 -1
  14. package/public/sw.js +5 -2
  15. package/src/app.ts +6 -0
  16. package/src/builder/find-workspace.ts +120 -0
  17. package/src/builder/home.ts +2 -0
  18. package/src/builder/mail.ts +39 -0
  19. package/src/builder/region-settings.ts +19 -1
  20. package/src/builder/rest-catalogs.ts +424 -0
  21. package/src/builder/routes.ts +32 -6
  22. package/src/builder/shared.ts +2 -0
  23. package/src/builder/workspace-transfer.ts +136 -0
  24. package/src/i18n/ar.ts +14 -0
  25. package/src/i18n/cs.ts +14 -0
  26. package/src/i18n/da.ts +14 -0
  27. package/src/i18n/de.ts +14 -0
  28. package/src/i18n/el.ts +14 -0
  29. package/src/i18n/es.ts +14 -0
  30. package/src/i18n/fi.ts +14 -0
  31. package/src/i18n/fr.ts +14 -0
  32. package/src/i18n/he.ts +14 -0
  33. package/src/i18n/it.ts +14 -0
  34. package/src/i18n/ja.ts +14 -0
  35. package/src/i18n/ko.ts +14 -0
  36. package/src/i18n/nb.ts +14 -0
  37. package/src/i18n/pl.ts +14 -0
  38. package/src/i18n/pt.ts +14 -0
  39. package/src/i18n/ru.ts +14 -0
  40. package/src/i18n/sv.ts +14 -0
  41. package/src/i18n/tr.ts +14 -0
  42. package/src/i18n/uk.ts +14 -0
  43. package/src/i18n/zh.ts +14 -0
  44. package/src/i18n.ts +28 -0
  45. package/src/restcatalog.ts +605 -0
  46. package/src/runtime/charts.ts +296 -12
  47. package/src/runtime/data-reporter.ts +48 -16
  48. package/src/runtime/gantt-move.ts +63 -0
  49. package/src/runtime/regions.ts +3 -0
  50. package/src/runtime/routes.ts +39 -0
  51. package/src/websources.ts +11 -3
  52. package/src/workspace-export.ts +240 -0
@@ -0,0 +1,136 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import type { FastifyInstance, FastifyReply } from 'fastify';
4
+ import { owner } from '../db.ts';
5
+ import { root as installRoot } from '../env.ts';
6
+ import { html } from '../html.ts';
7
+ import { icon } from '../icons.ts';
8
+ import { readMultipart } from '../runtime/files.ts';
9
+ import { clientIp, logActivity, type Session } from '../session.ts';
10
+ import { describeWorkspaceFile, exportWorkspace, importWorkspace, PART_LABELS, PARTS, type Part } from '../workspace-export.ts';
11
+ import { back, BASE, csrf, developer, flash, region, send, shell, type Body, type Req } from './ui.ts';
12
+ import { currentWorkspace, workspaceAdmin } from './workspaces.ts';
13
+
14
+ // Workspace utilities → Export / import (APEX: Workspace Utilities → Export):
15
+ // the current workspace's components as one file, and such a file imported
16
+ // into the current workspace (src/workspace-export.ts). For administrators and
17
+ // the workspace's administrators: the file names its developers and holds its
18
+ // SQL scripts.
19
+
20
+ const RESULT = '__WS_IMPORT_RESULT';
21
+ const page = `${BASE}/workspace-export`;
22
+ const crumbs: [string, string?][] = [['App Builder', BASE], ['Workspace utilities', `${BASE}/utilities`], ['Export and import']];
23
+ const MAX_MB = 20;
24
+
25
+ let version: string | null | undefined;
26
+ const pgkilnVersion = () => {
27
+ if (version !== undefined) return version;
28
+ try {
29
+ version = JSON.parse(readFileSync(join(installRoot, 'package.json'), 'utf8')).version ?? null;
30
+ } catch {
31
+ version = null;
32
+ }
33
+ return version;
34
+ };
35
+
36
+ const chosen = (b: Body | undefined): Part[] => {
37
+ const v = b?.part as unknown;
38
+ const list = Array.isArray(v) ? v : v == null ? [] : [v];
39
+ return PARTS.filter((p) => list.includes(p));
40
+ };
41
+
42
+ async function adminOr403(s: Session, reply: FastifyReply) {
43
+ if (s.workspace && (await workspaceAdmin(s.username, s.workspace.id))) return true;
44
+ await send(reply.code(403), s, shell(s, 'Export and import', crumbs, html`<h1 class="u-mb1">Export and import</h1>
45
+ <div class="alert alert-error" role="alert">Only administrators and the workspace's administrators export or import a workspace's components.</div>`));
46
+ return false;
47
+ }
48
+
49
+ const partBoxes = (counts?: Partial<Record<Part, number>>) =>
50
+ html`<fieldset class="field" data-wide><legend class="label">Components</legend>${PARTS.map((p) =>
51
+ counts && counts[p] === undefined
52
+ ? ''
53
+ : html`<label class="check"><input type="checkbox" name="part" value="${p}" checked> ${PART_LABELS[p]}${counts ? html` <span class="muted">(${p === 'settings' ? 'in the file' : counts[p]})</span>` : ''}</label>`)}</fieldset>`;
54
+
55
+ export async function workspaceTransferRoutes(app: FastifyInstance) {
56
+ app.get(page, async (req: Req, reply) => {
57
+ const s = await developer(req, reply);
58
+ if (!s || !(await adminOr403(s, reply))) return;
59
+ const result = s.state[RESULT] ? (JSON.parse(s.state[RESULT]!) as string[]) : null;
60
+ delete s.state[RESULT];
61
+ const name = s.workspace!.name;
62
+ const main = html`<div class="title-row"><h1>Export and import</h1></div>
63
+ <p class="muted u-mt0">The components of the workspace ${name} as one file: to copy them to another workspace or installation, or to keep them with your applications' files. Applications are exported on their own (App Builder → an application → Export).</p>
64
+ ${result ? html`<div class="alert alert-success" role="status"><b>Imported into ${name}:</b><ul>${result.map((l) => html`<li>${l}</li>`)}</ul></div>` : ''}
65
+ ${region('Export', html`<form method="post" action="${page}">${csrf(s)}
66
+ ${partBoxes()}
67
+ <p class="muted">Not in the file: the workspace's name, its isolation and database roles, passwords and secrets, script runs and other history.</p>
68
+ <div class="buttons"><button class="btn btn-hot">${icon('download')} Export</button></div></form>`)}
69
+ ${region('Import', html`<form method="post" action="${BASE}/workspace-import" enctype="multipart/form-data">${csrf(s)}
70
+ <p class="muted u-mt0">A file exported from a workspace, imported into <b>${name}</b>. Nothing is deleted; developers are added only when this installation has a developer of that name.</p>
71
+ <div class="form-grid">
72
+ <div class="field" data-wide><label class="label" for="f_file">Workspace file</label><input id="f_file" type="file" name="file" accept=".json,application/json" required></div>
73
+ ${partBoxes()}
74
+ <div class="field" data-wide><label class="check"><input type="checkbox" name="replace" value="true"> Replace components with the same name (otherwise this workspace's own are kept)</label></div>
75
+ </div>
76
+ <div class="buttons"><button class="btn">${icon('upload')} Import</button></div></form>`)}`;
77
+ return send(reply, s, shell(s, 'Export and import', crumbs, main));
78
+ });
79
+
80
+ app.post(page, async (req: Req, reply) => {
81
+ const s = await developer(req, reply);
82
+ if (!s || !(await adminOr403(s, reply))) return;
83
+ const parts = chosen(req.body);
84
+ if (!parts.length) {
85
+ flash(s, 'Choose at least one kind of component.', 'error');
86
+ return back(reply, s, page);
87
+ }
88
+ const doc = await exportWorkspace(currentWorkspace(s), parts, { version: pgkilnVersion() ?? undefined, by: s.username });
89
+ await logActivity({ appId: null, username: s.username, event: 'workspace', ip: clientIp(req), detail: `exported workspace ${currentWorkspace(s)}: ${parts.join(', ')}` });
90
+ const file = `${s.workspace!.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'workspace'}.workspace.json`;
91
+ return reply.type('application/json').header('content-disposition', `attachment; filename="${file}"`).send(JSON.stringify(doc, null, 2));
92
+ });
93
+
94
+ app.post(`${BASE}/workspace-import`, async (req: Req, reply) => {
95
+ let text = '';
96
+ let tooBig = false;
97
+ if (req.isMultipart()) {
98
+ const parsed = await readMultipart(req, MAX_MB);
99
+ req.body = parsed.body as Body;
100
+ const f = parsed.files.get('file');
101
+ tooBig = !!f?.truncated;
102
+ if (f && !f.truncated) text = f.data.toString('utf8');
103
+ }
104
+ const s = await developer(req, reply);
105
+ if (!s || !(await adminOr403(s, reply))) return;
106
+ try {
107
+ if (tooBig) throw new Error(`The file is larger than ${MAX_MB} MB.`);
108
+ if (!text.trim()) throw new Error('Choose a workspace file.');
109
+ let doc: unknown;
110
+ try {
111
+ doc = JSON.parse(text);
112
+ } catch {
113
+ throw new Error('The file is not valid JSON.');
114
+ }
115
+ describeWorkspaceFile(doc);
116
+ const parts = chosen(req.body);
117
+ if (!parts.length) throw new Error('Choose at least one kind of component.');
118
+ const ws = currentWorkspace(s);
119
+ const result = await owner.tx((c) => importWorkspace(c, ws, doc, parts, { replace: req.body?.replace === 'true', by: s.username }));
120
+ await logActivity({ appId: null, username: s.username, event: 'workspace', ip: clientIp(req), detail: `imported into workspace ${ws}: ${result.added} added, ${result.replaced} replaced, ${result.skipped} kept` });
121
+ s.state[RESULT] = JSON.stringify(result.lines);
122
+ } catch (e) {
123
+ flash(s, `Import failed, nothing was changed: ${friendly(e)}`, 'error');
124
+ }
125
+ return back(reply, s, page);
126
+ });
127
+ }
128
+
129
+ /** A database refusal in words (the transaction rolled back). */
130
+ function friendly(e: unknown) {
131
+ const err = e as { message: string; constraint?: string; table?: string; code?: string };
132
+ if (err.code === '23514' || err.code === '23502' || err.code === '22P02') return `the file has a value this installation refuses (${err.table ?? ''}${err.constraint ? `: ${err.constraint}` : ''}).`;
133
+ if (err.code === '23505') return `the file has two components with the same name (${err.constraint ?? err.table ?? ''}).`;
134
+ return err.message;
135
+ }
136
+
package/src/i18n/ar.ts CHANGED
@@ -496,6 +496,13 @@ export const ar: Record<MessageKey, string> = {
496
496
  'chart.no_range': "يحتاج مخطط النطاق إلى عمود تسميات وعمودين رقميين: الأدنى والأعلى.",
497
497
  'chart.no_boxplot': "يحتاج المخطط الصندوقي إلى عمود تسميات وإما عمود رقمي واحد (صف لكل قيمة) أو خمسة: الحد الأدنى والربيع الأول والوسيط والربيع الثالث والحد الأقصى.",
498
498
  'chart.no_stock': "يحتاج مخطط الأسهم إلى عمود تسميات (التاريخ) وأربعة أعمدة رقمية: الافتتاح والأعلى والأدنى والإغلاق.",
499
+ 'chart.no_tree': "يحتاج مخطط الخريطة الشجرية أو المخطط الشمسي إلى عمود تسميات وعمود رقمي (واختياريًا عمودي node_id وparent_id للتسلسل الهرمي).",
500
+ 'chart.no_timeline': "يحتاج المخطط الزمني إلى عمود تسميات وتاريخ بداية (تاريخ النهاية وعمود series اختياريان).",
501
+ 'chart.total': "الإجمالي",
502
+ 'chart.gantt_hint': "اسحب مهمة لنقلها، أو نهايتها لتغيير مدتها. بلوحة المفاتيح: تنقل الأسهم المهمة، ويغيّر Shift مع الأسهم نهايتها، وEnter يحفظ، وEscape يلغي.",
503
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
504
+ 'chart.gantt_cannot_move': "لا يمكن نقل هذه المهمة.",
505
+ 'chart.gantt_bad_dates': "اختر بداية ونهاية في يوم البداية أو بعده.",
499
506
  'chart.min': "الحد الأدنى",
500
507
  'chart.q1': "الربيع الأول",
501
508
  'chart.median': "الوسيط",
@@ -652,6 +659,13 @@ export const ar: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "أعطِ التقرير اسمًا.",
653
660
  'reporter.not_found': "هذا التقرير غير متاح.",
654
661
  'reporter.unknown_source': "مصدر البيانات هذا لم يعد متاحًا.",
662
+ 'reporter.welcome_title': "أنشئ تقاريرك الخاصة",
663
+ 'reporter.welcome_text': "اختر البيانات لبدء تقرير: حدد الأعمدة، صفِّ، جمّع مع المجاميع، أضف مخططًا، ثم احفظه أو شاركه.",
664
+ 'reporter.columns_count': "{n} أعمدة",
665
+ 'reporter.search': "البحث في التقارير",
666
+ 'reporter.changed': "عُدِّل في {date}",
667
+ 'reporter.no_match': "لا توجد تقارير مطابقة للبحث.",
668
+ 'reporter.chart_tag': "مخطط",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "المحادثة",
657
671
  'assistant.you': "أنت",
package/src/i18n/cs.ts CHANGED
@@ -496,6 +496,13 @@ export const cs: Record<MessageKey, string> = {
496
496
  'chart.no_range': "Graf rozsahu potřebuje sloupec popisků a dva číselné sloupce: dolní a horní hodnotu.",
497
497
  'chart.no_boxplot': "Krabicový graf potřebuje sloupec popisků a buď jeden číselný sloupec (řádek pro každou hodnotu), nebo pět: minimum, první kvartil, medián, třetí kvartil a maximum.",
498
498
  'chart.no_stock': "Burzovní graf potřebuje sloupec popisků (datum) a čtyři číselné sloupce: otevření, maximum, minimum a uzavření.",
499
+ 'chart.no_tree': "Stromová mapa nebo sunburst potřebuje sloupec popisků a číselný sloupec (volitelně sloupce node_id a parent_id pro hierarchii).",
500
+ 'chart.no_timeline': "Časová osa potřebuje sloupec popisků a datum začátku (datum konce a sloupec series jsou volitelné).",
501
+ 'chart.total': "Celkem",
502
+ 'chart.gantt_hint': "Přetažením úkolu ho přesunete, přetažením jeho konce změníte délku. Klávesnicí: šipky přesouvají úkol, Shift se šipkami mění konec, Enter ukládá, Escape ruší.",
503
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
504
+ 'chart.gantt_cannot_move': "Tento úkol nelze přesunout.",
505
+ 'chart.gantt_bad_dates': "Zvolte začátek a konec ve stejný den nebo po začátku.",
499
506
  'chart.min': "Minimum",
500
507
  'chart.q1': "První kvartil",
501
508
  'chart.median': "Medián",
@@ -652,6 +659,13 @@ export const cs: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "Pojmenujte sestavu.",
653
660
  'reporter.not_found': "Tato sestava není k dispozici.",
654
661
  'reporter.unknown_source': "Tento zdroj dat už není k dispozici.",
662
+ 'reporter.welcome_title': "Vytvořte si vlastní sestavy",
663
+ 'reporter.welcome_text': "Vyberte data pro novou sestavu: sloupce, filtry, seskupení se součty a graf, potom ji uložte nebo sdílejte.",
664
+ 'reporter.columns_count': "{n} sloupců",
665
+ 'reporter.search': "Hledat sestavy",
666
+ 'reporter.changed': "změněno {date}",
667
+ 'reporter.no_match': "Hledání neodpovídá žádná sestava.",
668
+ 'reporter.chart_tag': "graf",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "Konverzace",
657
671
  'assistant.you': "Vy",
package/src/i18n/da.ts CHANGED
@@ -496,6 +496,13 @@ export const da: Record<MessageKey, string> = {
496
496
  'chart.no_range': "Et intervaldiagram kræver en etiketkolonne og to numeriske kolonner: lav og høj.",
497
497
  'chart.no_boxplot': "Et boksplot kræver en etiketkolonne og enten én numerisk kolonne (en række pr. værdi) eller fem: minimum, første kvartil, median, tredje kvartil og maksimum.",
498
498
  'chart.no_stock': "Et aktiediagram kræver en etiketkolonne (datoen) og fire numeriske kolonner: åbning, høj, lav og lukning.",
499
+ 'chart.no_tree': "Et trækort eller sunburst kræver en etiketkolonne og en numerisk kolonne (eventuelt kolonnerne node_id og parent_id for et hierarki).",
500
+ 'chart.no_timeline': "En tidslinje kræver en etiketkolonne og en startdato (en slutdato og en series-kolonne er valgfrie).",
501
+ 'chart.total': "I alt",
502
+ 'chart.gantt_hint': "Træk en opgave for at flytte den, eller dens slutning for at ændre varigheden. Med tastaturet: piletasterne flytter en opgave, Skift og piletasterne ændrer slutningen, Enter gemmer, Escape annullerer.",
503
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
504
+ 'chart.gantt_cannot_move': "Denne opgave kan ikke flyttes.",
505
+ 'chart.gantt_bad_dates': "Vælg en start og en slutning på eller efter starten.",
499
506
  'chart.min': "Minimum",
500
507
  'chart.q1': "Første kvartil",
501
508
  'chart.median': "Median",
@@ -652,6 +659,13 @@ export const da: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "Giv rapporten et navn.",
653
660
  'reporter.not_found': "Den rapport er ikke tilgængelig.",
654
661
  'reporter.unknown_source': "Denne datakilde er ikke længere tilgængelig.",
662
+ 'reporter.welcome_title': "Lav dine egne rapporter",
663
+ 'reporter.welcome_text': "Vælg data for at starte en rapport: vælg kolonner, filtrér, gruppér med totaler, tilføj et diagram, og gem eller del den.",
664
+ 'reporter.columns_count': "{n} kolonner",
665
+ 'reporter.search': "Søg i rapporter",
666
+ 'reporter.changed': "ændret {date}",
667
+ 'reporter.no_match': "Ingen rapporter matcher søgningen.",
668
+ 'reporter.chart_tag': "diagram",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "Samtale",
657
671
  'assistant.you': "Dig",
package/src/i18n/de.ts CHANGED
@@ -457,6 +457,13 @@ export const de: Record<MessageKey, string> = {
457
457
  'chart.no_range': 'Ein Bereichsdiagramm benötigt eine Beschriftungsspalte und zwei numerische Spalten: Tief und Hoch.',
458
458
  'chart.no_boxplot': 'Ein Boxplot benötigt eine Beschriftungsspalte und entweder eine numerische Spalte (eine Zeile pro Wert) oder fünf: Minimum, erstes Quartil, Median, drittes Quartil und Maximum.',
459
459
  'chart.no_stock': 'Ein Kursdiagramm benötigt eine Beschriftungsspalte (das Datum) und vier numerische Spalten: Eröffnung, Hoch, Tief und Schluss.',
460
+ 'chart.no_tree': "Eine Treemap oder ein Sunburst braucht eine Beschriftungsspalte und eine numerische Spalte (optional die Spalten node_id und parent_id für eine Hierarchie).",
461
+ 'chart.no_timeline': "Eine Zeitleiste braucht eine Beschriftungsspalte und ein Startdatum (ein Enddatum und eine Spalte series sind optional).",
462
+ 'chart.total': "Gesamt",
463
+ 'chart.gantt_hint': "Ziehen Sie eine Aufgabe, um sie zu verschieben, oder ihr Ende, um die Dauer zu ändern. Mit der Tastatur: Pfeiltasten verschieben eine Aufgabe, Umschalt mit Pfeiltasten ändert das Ende, Eingabe speichert, Escape bricht ab.",
464
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
465
+ 'chart.gantt_cannot_move': "Diese Aufgabe kann nicht verschoben werden.",
466
+ 'chart.gantt_bad_dates': "Wählen Sie einen Beginn und ein Ende am oder nach dem Beginn.",
460
467
  'chart.min': 'Minimum',
461
468
  'chart.q1': 'Erstes Quartil',
462
469
  'chart.median': 'Median',
@@ -652,6 +659,13 @@ export const de: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "Geben Sie dem Bericht einen Namen.",
653
660
  'reporter.not_found': "Dieser Bericht ist nicht verfügbar.",
654
661
  'reporter.unknown_source': "Diese Datenquelle ist nicht mehr verfügbar.",
662
+ 'reporter.welcome_title': "Eigene Berichte erstellen",
663
+ 'reporter.welcome_text': "Wählen Sie die Daten für einen neuen Bericht: Spalten auswählen, filtern, mit Summen gruppieren, ein Diagramm hinzufügen und den Bericht speichern oder teilen.",
664
+ 'reporter.columns_count': "{n} Spalten",
665
+ 'reporter.search': "Berichte suchen",
666
+ 'reporter.changed': "geändert {date}",
667
+ 'reporter.no_match': "Keine Berichte gefunden.",
668
+ 'reporter.chart_tag': "Diagramm",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "Unterhaltung",
657
671
  'assistant.you': "Sie",
package/src/i18n/el.ts CHANGED
@@ -496,6 +496,13 @@ export const el: Record<MessageKey, string> = {
496
496
  'chart.no_range': "Ένα γράφημα εύρους χρειάζεται μια στήλη ετικετών και δύο αριθμητικές στήλες: χαμηλή και υψηλή.",
497
497
  'chart.no_boxplot': "Ένα θηκόγραμμα χρειάζεται μια στήλη ετικετών και είτε μία αριθμητική στήλη (μία γραμμή ανά τιμή) είτε πέντε: ελάχιστο, πρώτο τεταρτημόριο, διάμεσο, τρίτο τεταρτημόριο και μέγιστο.",
498
498
  'chart.no_stock': "Ένα γράφημα μετοχών χρειάζεται μια στήλη ετικετών (την ημερομηνία) και τέσσερις αριθμητικές στήλες: άνοιγμα, υψηλό, χαμηλό και κλείσιμο.",
499
+ 'chart.no_tree': "Ένας χάρτης δέντρου ή ένα sunburst χρειάζεται στήλη ετικετών και αριθμητική στήλη (προαιρετικά στήλες node_id και parent_id για ιεραρχία).",
500
+ 'chart.no_timeline': "Ένα χρονολόγιο χρειάζεται στήλη ετικετών και ημερομηνία έναρξης (η ημερομηνία λήξης και μια στήλη series είναι προαιρετικές).",
501
+ 'chart.total': "Σύνολο",
502
+ 'chart.gantt_hint': "Σύρετε μια εργασία για να τη μετακινήσετε ή το τέλος της για να αλλάξετε τη διάρκεια. Με το πληκτρολόγιο: τα βέλη μετακινούν μια εργασία, Shift με τα βέλη αλλάζει το τέλος, Enter αποθηκεύει, Escape ακυρώνει.",
503
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
504
+ 'chart.gantt_cannot_move': "Αυτή η εργασία δεν μπορεί να μετακινηθεί.",
505
+ 'chart.gantt_bad_dates': "Επιλέξτε έναρξη και λήξη ίση ή μεταγενέστερη της έναρξης.",
499
506
  'chart.min': "Ελάχιστο",
500
507
  'chart.q1': "Πρώτο τεταρτημόριο",
501
508
  'chart.median': "Διάμεσος",
@@ -652,6 +659,13 @@ export const el: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "Δώστε όνομα στην αναφορά.",
653
660
  'reporter.not_found': "Αυτή η αναφορά δεν είναι διαθέσιμη.",
654
661
  'reporter.unknown_source': "Αυτή η πηγή δεδομένων δεν είναι πλέον διαθέσιμη.",
662
+ 'reporter.welcome_title': "Δημιουργήστε τις δικές σας αναφορές",
663
+ 'reporter.welcome_text': "Επιλέξτε τα δεδομένα για μια νέα αναφορά: στήλες, φίλτρα, ομαδοποίηση με σύνολα, ένα γράφημα, και αποθηκεύστε ή μοιραστείτε την.",
664
+ 'reporter.columns_count': "{n} στήλες",
665
+ 'reporter.search': "Αναζήτηση αναφορών",
666
+ 'reporter.changed': "άλλαξε {date}",
667
+ 'reporter.no_match': "Καμία αναφορά δεν ταιριάζει με την αναζήτηση.",
668
+ 'reporter.chart_tag': "γράφημα",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "Συνομιλία",
657
671
  'assistant.you': "Εσείς",
package/src/i18n/es.ts CHANGED
@@ -457,6 +457,13 @@ export const es: Record<MessageKey, string> = {
457
457
  'chart.no_range': 'Un gráfico de rango necesita una columna de etiquetas y dos columnas numéricas: mínimo y máximo.',
458
458
  'chart.no_boxplot': 'Un diagrama de caja necesita una columna de etiquetas y una columna numérica (una fila por valor) o cinco: mínimo, primer cuartil, mediana, tercer cuartil y máximo.',
459
459
  'chart.no_stock': 'Un gráfico bursátil necesita una columna de etiquetas (la fecha) y cuatro columnas numéricas: apertura, máximo, mínimo y cierre.',
460
+ 'chart.no_tree': "Un treemap o sunburst necesita una columna de etiquetas y una columna numérica (y opcionalmente las columnas node_id y parent_id para una jerarquía).",
461
+ 'chart.no_timeline': "Una línea de tiempo necesita una columna de etiquetas y una fecha de inicio (una fecha de fin y una columna series son opcionales).",
462
+ 'chart.total': "Total",
463
+ 'chart.gantt_hint': "Arrastre una tarea para moverla, o su final para cambiar su duración. Con el teclado: las flechas mueven una tarea, Mayús y las flechas cambian su final, Intro guarda, Escape cancela.",
464
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
465
+ 'chart.gantt_cannot_move': "Esta tarea no se puede mover.",
466
+ 'chart.gantt_bad_dates': "Elija un inicio y un final igual o posterior al inicio.",
460
467
  'chart.min': 'Mínimo',
461
468
  'chart.q1': 'Primer cuartil',
462
469
  'chart.median': 'Mediana',
@@ -652,6 +659,13 @@ export const es: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "Dé un nombre al informe.",
653
660
  'reporter.not_found': "Ese informe no está disponible.",
654
661
  'reporter.unknown_source': "Este origen de datos ya no está disponible.",
662
+ 'reporter.welcome_title': "Cree sus propios informes",
663
+ 'reporter.welcome_text': "Elija los datos para empezar un informe: columnas, filtros, agrupaciones con totales y un gráfico; después guárdelo o compártalo.",
664
+ 'reporter.columns_count': "{n} columnas",
665
+ 'reporter.search': "Buscar informes",
666
+ 'reporter.changed': "modificado el {date}",
667
+ 'reporter.no_match': "Ningún informe coincide con la búsqueda.",
668
+ 'reporter.chart_tag': "gráfico",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "Conversación",
657
671
  'assistant.you': "Usted",
package/src/i18n/fi.ts CHANGED
@@ -496,6 +496,13 @@ export const fi: Record<MessageKey, string> = {
496
496
  'chart.no_range': "Vaihteluvälikaavio tarvitsee otsikkosarakkeen ja kaksi numeerista saraketta: alin ja ylin.",
497
497
  'chart.no_boxplot': "Laatikkokaavio tarvitsee otsikkosarakkeen ja joko yhden numeerisen sarakkeen (rivi arvoa kohden) tai viisi: minimi, ensimmäinen kvartiili, mediaani, kolmas kvartiili ja maksimi.",
498
498
  'chart.no_stock': "Kurssikaavio tarvitsee otsikkosarakkeen (päivämäärä) ja neljä numeerista saraketta: avaus, ylin, alin ja päätös.",
499
+ 'chart.no_tree': "Puukartta tai sunburst tarvitsee nimikesarakkeen ja numeerisen sarakkeen (hierarkiaa varten myös sarakkeet node_id ja parent_id).",
500
+ 'chart.no_timeline': "Aikajana tarvitsee nimikesarakkeen ja alkupäivän (loppupäivä ja series-sarake ovat valinnaisia).",
501
+ 'chart.total': "Yhteensä",
502
+ 'chart.gantt_hint': "Siirrä tehtävää vetämällä tai muuta sen kestoa vetämällä sen loppua. Näppäimistöllä: nuolinäppäimet siirtävät tehtävää, Vaihto ja nuolet muuttavat loppua, Enter tallentaa, Esc peruuttaa.",
503
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
504
+ 'chart.gantt_cannot_move': "Tätä tehtävää ei voi siirtää.",
505
+ 'chart.gantt_bad_dates': "Valitse alku ja loppu, joka on alun jälkeen tai samana päivänä.",
499
506
  'chart.min': "Minimi",
500
507
  'chart.q1': "Ensimmäinen kvartiili",
501
508
  'chart.median': "Mediaani",
@@ -652,6 +659,13 @@ export const fi: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "Anna raportille nimi.",
653
660
  'reporter.not_found': "Raportti ei ole saatavilla.",
654
661
  'reporter.unknown_source': "Tämä tietolähde ei ole enää saatavilla.",
662
+ 'reporter.welcome_title': "Tee omia raportteja",
663
+ 'reporter.welcome_text': "Valitse tiedot uutta raporttia varten: sarakkeet, suodattimet, ryhmittely summineen ja kaavio, ja tallenna tai jaa raportti.",
664
+ 'reporter.columns_count': "{n} saraketta",
665
+ 'reporter.search': "Hae raportteja",
666
+ 'reporter.changed': "muutettu {date}",
667
+ 'reporter.no_match': "Haulla ei löytynyt raportteja.",
668
+ 'reporter.chart_tag': "kaavio",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "Keskustelu",
657
671
  'assistant.you': "Sinä",
package/src/i18n/fr.ts CHANGED
@@ -457,6 +457,13 @@ export const fr: Record<MessageKey, string> = {
457
457
  'chart.no_range': 'Un graphique de plage nécessite une colonne de libellés et deux colonnes numériques : bas et haut.',
458
458
  'chart.no_boxplot': 'Une boîte à moustaches nécessite une colonne de libellés et soit une colonne numérique (une ligne par valeur), soit cinq : minimum, premier quartile, médiane, troisième quartile et maximum.',
459
459
  'chart.no_stock': 'Un graphique boursier nécessite une colonne de libellés (la date) et quatre colonnes numériques : ouverture, haut, bas et clôture.',
460
+ 'chart.no_tree': "Un treemap ou un sunburst nécessite une colonne de libellés et une colonne numérique (et éventuellement les colonnes node_id et parent_id pour une hiérarchie).",
461
+ 'chart.no_timeline': "Une chronologie nécessite une colonne de libellés et une date de début (une date de fin et une colonne series sont facultatives).",
462
+ 'chart.total': "Total",
463
+ 'chart.gantt_hint': "Faites glisser une tâche pour la déplacer, ou sa fin pour changer sa durée. Au clavier : les flèches déplacent une tâche, Maj et les flèches changent sa fin, Entrée enregistre, Échap annule.",
464
+ 'chart.gantt_moved': "{task} : {start} – {end}.",
465
+ 'chart.gantt_cannot_move': "Cette tâche ne peut pas être déplacée.",
466
+ 'chart.gantt_bad_dates': "Choisissez un début et une fin égale ou postérieure au début.",
460
467
  'chart.min': 'Minimum',
461
468
  'chart.q1': 'Premier quartile',
462
469
  'chart.median': 'Médiane',
@@ -652,6 +659,13 @@ export const fr: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "Donnez un nom au rapport.",
653
660
  'reporter.not_found': "Ce rapport n'est pas disponible.",
654
661
  'reporter.unknown_source': "Cette source de données n'est plus disponible.",
662
+ 'reporter.welcome_title': "Créez vos propres rapports",
663
+ 'reporter.welcome_text': "Choisissez les données d'un nouveau rapport : colonnes, filtres, regroupements avec totaux, un graphique, puis enregistrez-le ou partagez-le.",
664
+ 'reporter.columns_count': "{n} colonnes",
665
+ 'reporter.search': "Rechercher des rapports",
666
+ 'reporter.changed': "modifié le {date}",
667
+ 'reporter.no_match': "Aucun rapport ne correspond à la recherche.",
668
+ 'reporter.chart_tag': "graphique",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "Conversation",
657
671
  'assistant.you': "Vous",
package/src/i18n/he.ts CHANGED
@@ -496,6 +496,13 @@ export const he: Record<MessageKey, string> = {
496
496
  'chart.no_range': "תרשים טווח צריך עמודת תוויות ושתי עמודות מספריות: נמוך וגבוה.",
497
497
  'chart.no_boxplot': "תרשים קופסה צריך עמודת תוויות ועמודה מספרית אחת (שורה לכל ערך) או חמש: מינימום, רבעון ראשון, חציון, רבעון שלישי ומקסימום.",
498
498
  'chart.no_stock': "תרשים מניות צריך עמודת תוויות (התאריך) וארבע עמודות מספריות: פתיחה, גבוה, נמוך וסגירה.",
499
+ 'chart.no_tree': "מפת עץ או תרשים sunburst צריכים עמודת תוויות ועמודה מספרית (ולהיררכיה גם עמודות node_id ו-parent_id).",
500
+ 'chart.no_timeline': "ציר זמן צריך עמודת תוויות ותאריך התחלה (תאריך סיום ועמודת series הם אופציונליים).",
501
+ 'chart.total': "סה״כ",
502
+ 'chart.gantt_hint': "גררו משימה כדי להזיז אותה, או את סופה כדי לשנות את משכה. במקלדת: החצים מזיזים משימה, Shift עם החצים משנה את סופה, Enter שומר, Escape מבטל.",
503
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
504
+ 'chart.gantt_cannot_move': "לא ניתן להזיז משימה זו.",
505
+ 'chart.gantt_bad_dates': "בחרו התחלה וסיום שאינו לפני ההתחלה.",
499
506
  'chart.min': "מינימום",
500
507
  'chart.q1': "רבעון ראשון",
501
508
  'chart.median': "חציון",
@@ -652,6 +659,13 @@ export const he: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "תנו שם לדוח.",
653
660
  'reporter.not_found': "הדוח הזה אינו זמין.",
654
661
  'reporter.unknown_source': "מקור הנתונים הזה כבר אינו זמין.",
662
+ 'reporter.welcome_title': "בנו דוחות משלכם",
663
+ 'reporter.welcome_text': "בחרו את הנתונים כדי להתחיל דוח: עמודות, סינון, קיבוץ עם סיכומים ותרשים, ואז שמרו או שתפו אותו.",
664
+ 'reporter.columns_count': "{n} עמודות",
665
+ 'reporter.search': "חיפוש דוחות",
666
+ 'reporter.changed': "שונה ב-{date}",
667
+ 'reporter.no_match': "אין דוחות התואמים לחיפוש.",
668
+ 'reporter.chart_tag': "תרשים",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "שיחה",
657
671
  'assistant.you': "את/ה",
package/src/i18n/it.ts CHANGED
@@ -496,6 +496,13 @@ export const it: Record<MessageKey, string> = {
496
496
  'chart.no_range': "Un grafico a intervalli richiede una colonna di etichette e due colonne numeriche: minimo e massimo.",
497
497
  'chart.no_boxplot': "Un box plot richiede una colonna di etichette e una colonna numerica (una riga per valore) oppure cinque: minimo, primo quartile, mediana, terzo quartile e massimo.",
498
498
  'chart.no_stock': "Un grafico azionario richiede una colonna di etichette (la data) e quattro colonne numeriche: apertura, massimo, minimo e chiusura.",
499
+ 'chart.no_tree': "Una treemap o un sunburst richiede una colonna di etichette e una colonna numerica (facoltativamente le colonne node_id e parent_id per una gerarchia).",
500
+ 'chart.no_timeline': "Una timeline richiede una colonna di etichette e una data di inizio (una data di fine e una colonna series sono facoltative).",
501
+ 'chart.total': "Totale",
502
+ 'chart.gantt_hint': "Trascina un'attività per spostarla, o la sua fine per cambiarne la durata. Da tastiera: le frecce spostano un'attività, Maiusc con le frecce ne cambia la fine, Invio salva, Esc annulla.",
503
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
504
+ 'chart.gantt_cannot_move': "Questa attività non può essere spostata.",
505
+ 'chart.gantt_bad_dates': "Scegli un inizio e una fine uguale o successiva all'inizio.",
499
506
  'chart.min': "Minimo",
500
507
  'chart.q1': "Primo quartile",
501
508
  'chart.median': "Mediana",
@@ -652,6 +659,13 @@ export const it: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "Dai un nome al report.",
653
660
  'reporter.not_found': "Quel report non è disponibile.",
654
661
  'reporter.unknown_source': "Questa fonte di dati non è più disponibile.",
662
+ 'reporter.welcome_title': "Crea i tuoi report",
663
+ 'reporter.welcome_text': "Scegli i dati per iniziare un report: colonne, filtri, raggruppamenti con totali e un grafico, poi salvalo o condividilo.",
664
+ 'reporter.columns_count': "{n} colonne",
665
+ 'reporter.search': "Cerca report",
666
+ 'reporter.changed': "modificato il {date}",
667
+ 'reporter.no_match': "Nessun report corrisponde alla ricerca.",
668
+ 'reporter.chart_tag': "grafico",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "Conversazione",
657
671
  'assistant.you': "Tu",
package/src/i18n/ja.ts CHANGED
@@ -496,6 +496,13 @@ export const ja: Record<MessageKey, string> = {
496
496
  'chart.no_range': "範囲グラフには、ラベル列と 2 つの数値列(下限と上限)が必要です。",
497
497
  'chart.no_boxplot': "箱ひげ図には、ラベル列と、1 つの数値列(値ごとに 1 行)または 5 つの数値列(最小値、第 1 四分位数、中央値、第 3 四分位数、最大値)が必要です。",
498
498
  'chart.no_stock': "株価グラフには、ラベル列(日付)と 4 つの数値列(始値、高値、安値、終値)が必要です。",
499
+ 'chart.no_tree': "ツリーマップまたはサンバーストには、ラベル列と数値列が必要です(階層には node_id と parent_id 列も使えます)。",
500
+ 'chart.no_timeline': "タイムラインにはラベル列と開始日が必要です(終了日と series 列は任意です)。",
501
+ 'chart.total': "合計",
502
+ 'chart.gantt_hint': "タスクをドラッグして移動し、終わりをドラッグして期間を変更します。キーボード:矢印キーでタスクを移動、Shift と矢印キーで終わりを変更、Enter で保存、Escape で取り消します。",
503
+ 'chart.gantt_moved': "{task}:{start} – {end}。",
504
+ 'chart.gantt_cannot_move': "このタスクは移動できません。",
505
+ 'chart.gantt_bad_dates': "開始日と、開始日以降の終了日を選んでください。",
499
506
  'chart.min': "最小値",
500
507
  'chart.q1': "第 1 四分位数",
501
508
  'chart.median': "中央値",
@@ -652,6 +659,13 @@ export const ja: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "レポートに名前を付けてください。",
653
660
  'reporter.not_found': "そのレポートは利用できません。",
654
661
  'reporter.unknown_source': "このデータソースはもう利用できません。",
662
+ 'reporter.welcome_title': "自分のレポートを作成",
663
+ 'reporter.welcome_text': "データを選んでレポートを始めます。列の選択、フィルター、合計付きのグループ化、グラフの追加を行い、保存または共有できます。",
664
+ 'reporter.columns_count': "{n} 列",
665
+ 'reporter.search': "レポートを検索",
666
+ 'reporter.changed': "{date} に変更",
667
+ 'reporter.no_match': "検索に一致するレポートはありません。",
668
+ 'reporter.chart_tag': "グラフ",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "会話",
657
671
  'assistant.you': "あなた",
package/src/i18n/ko.ts CHANGED
@@ -496,6 +496,13 @@ export const ko: Record<MessageKey, string> = {
496
496
  'chart.no_range': "범위 차트에는 레이블 열과 두 개의 숫자 열(최저값과 최고값)이 필요합니다.",
497
497
  'chart.no_boxplot': "상자 그림에는 레이블 열과 숫자 열 하나(값마다 한 행) 또는 다섯 개(최솟값, 제1사분위수, 중앙값, 제3사분위수, 최댓값)가 필요합니다.",
498
498
  'chart.no_stock': "주가 차트에는 레이블 열(날짜)과 네 개의 숫자 열(시가, 고가, 저가, 종가)이 필요합니다.",
499
+ 'chart.no_tree': "트리맵 또는 선버스트에는 레이블 열과 숫자 열이 필요합니다(계층에는 node_id와 parent_id 열을 선택적으로 사용).",
500
+ 'chart.no_timeline': "타임라인에는 레이블 열과 시작 날짜가 필요합니다(종료 날짜와 series 열은 선택 사항).",
501
+ 'chart.total': "합계",
502
+ 'chart.gantt_hint': "작업을 끌어 이동하거나 끝을 끌어 기간을 바꾸세요. 키보드: 화살표 키로 작업을 이동하고, Shift와 화살표 키로 끝을 바꾸며, Enter로 저장, Escape로 취소합니다.",
503
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
504
+ 'chart.gantt_cannot_move': "이 작업은 이동할 수 없습니다.",
505
+ 'chart.gantt_bad_dates': "시작과, 시작과 같거나 이후인 끝을 선택하세요.",
499
506
  'chart.min': "최솟값",
500
507
  'chart.q1': "제1사분위수",
501
508
  'chart.median': "중앙값",
@@ -652,6 +659,13 @@ export const ko: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "보고서 이름을 입력하세요.",
653
660
  'reporter.not_found': "그 보고서는 사용할 수 없습니다.",
654
661
  'reporter.unknown_source': "이 데이터 원본은 더 이상 사용할 수 없습니다.",
662
+ 'reporter.welcome_title': "나만의 보고서 만들기",
663
+ 'reporter.welcome_text': "데이터를 선택해 보고서를 시작하세요. 열 선택, 필터, 합계가 있는 그룹, 차트를 추가한 뒤 저장하거나 공유할 수 있습니다.",
664
+ 'reporter.columns_count': "열 {n}개",
665
+ 'reporter.search': "보고서 검색",
666
+ 'reporter.changed': "{date} 변경됨",
667
+ 'reporter.no_match': "검색과 일치하는 보고서가 없습니다.",
668
+ 'reporter.chart_tag': "차트",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "대화",
657
671
  'assistant.you': "나",
package/src/i18n/nb.ts CHANGED
@@ -496,6 +496,13 @@ export const nb: Record<MessageKey, string> = {
496
496
  'chart.no_range': "Et intervalldiagram trenger en etikettkolonne og to numeriske kolonner: lav og høy.",
497
497
  'chart.no_boxplot': "Et boksplott trenger en etikettkolonne og enten én numerisk kolonne (en rad per verdi) eller fem: minimum, første kvartil, median, tredje kvartil og maksimum.",
498
498
  'chart.no_stock': "Et aksjediagram trenger en etikettkolonne (datoen) og fire numeriske kolonner: åpning, høy, lav og slutt.",
499
+ 'chart.no_tree': "Et trekart eller sunburst trenger en etikettkolonne og en numerisk kolonne (eventuelt kolonnene node_id og parent_id for et hierarki).",
500
+ 'chart.no_timeline': "En tidslinje trenger en etikettkolonne og en startdato (en sluttdato og en series-kolonne er valgfrie).",
501
+ 'chart.total': "Totalt",
502
+ 'chart.gantt_hint': "Dra en oppgave for å flytte den, eller slutten for å endre varigheten. Med tastaturet: piltastene flytter en oppgave, Shift og piltastene endrer slutten, Enter lagrer, Escape avbryter.",
503
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
504
+ 'chart.gantt_cannot_move': "Denne oppgaven kan ikke flyttes.",
505
+ 'chart.gantt_bad_dates': "Velg en start og en slutt på eller etter starten.",
499
506
  'chart.min': "Minimum",
500
507
  'chart.q1': "Første kvartil",
501
508
  'chart.median': "Median",
@@ -652,6 +659,13 @@ export const nb: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "Gi rapporten et navn.",
653
660
  'reporter.not_found': "Den rapporten er ikke tilgjengelig.",
654
661
  'reporter.unknown_source': "Denne datakilden er ikke lenger tilgjengelig.",
662
+ 'reporter.welcome_title': "Lag dine egne rapporter",
663
+ 'reporter.welcome_text': "Velg data for å starte en rapport: velg kolonner, filtrer, grupper med summer, legg til et diagram, og lagre eller del den.",
664
+ 'reporter.columns_count': "{n} kolonner",
665
+ 'reporter.search': "Søk i rapporter",
666
+ 'reporter.changed': "endret {date}",
667
+ 'reporter.no_match': "Ingen rapporter samsvarer med søket.",
668
+ 'reporter.chart_tag': "diagram",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "Samtale",
657
671
  'assistant.you': "Du",
package/src/i18n/pl.ts CHANGED
@@ -496,6 +496,13 @@ export const pl: Record<MessageKey, string> = {
496
496
  'chart.no_range': "Wykres zakresowy wymaga kolumny etykiet i dwóch kolumn liczbowych: dolnej i górnej wartości.",
497
497
  'chart.no_boxplot': "Wykres pudełkowy wymaga kolumny etykiet oraz jednej kolumny liczbowej (wiersz na wartość) albo pięciu: minimum, pierwszy kwartyl, mediana, trzeci kwartyl i maksimum.",
498
498
  'chart.no_stock': "Wykres giełdowy wymaga kolumny etykiet (data) i czterech kolumn liczbowych: otwarcie, maksimum, minimum i zamknięcie.",
499
+ 'chart.no_tree': "Mapa drzewa lub wykres sunburst wymaga kolumny etykiet i kolumny liczbowej (opcjonalnie kolumn node_id i parent_id dla hierarchii).",
500
+ 'chart.no_timeline': "Oś czasu wymaga kolumny etykiet i daty początkowej (data końcowa i kolumna series są opcjonalne).",
501
+ 'chart.total': "Razem",
502
+ 'chart.gantt_hint': "Przeciągnij zadanie, aby je przesunąć, lub jego koniec, aby zmienić czas trwania. Klawiaturą: strzałki przesuwają zadanie, Shift ze strzałkami zmienia koniec, Enter zapisuje, Escape anuluje.",
503
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
504
+ 'chart.gantt_cannot_move': "Tego zadania nie można przesunąć.",
505
+ 'chart.gantt_bad_dates': "Wybierz początek i koniec nie wcześniejszy niż początek.",
499
506
  'chart.min': "Minimum",
500
507
  'chart.q1': "Pierwszy kwartyl",
501
508
  'chart.median': "Mediana",
@@ -652,6 +659,13 @@ export const pl: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "Nadaj raportowi nazwę.",
653
660
  'reporter.not_found': "Ten raport jest niedostępny.",
654
661
  'reporter.unknown_source': "To źródło danych nie jest już dostępne.",
662
+ 'reporter.welcome_title': "Twórz własne raporty",
663
+ 'reporter.welcome_text': "Wybierz dane, aby rozpocząć raport: kolumny, filtry, grupowanie z sumami i wykres, a potem zapisz go lub udostępnij.",
664
+ 'reporter.columns_count': "Kolumny: {n}",
665
+ 'reporter.search': "Szukaj raportów",
666
+ 'reporter.changed': "zmieniono {date}",
667
+ 'reporter.no_match': "Żaden raport nie pasuje do wyszukiwania.",
668
+ 'reporter.chart_tag': "wykres",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "Rozmowa",
657
671
  'assistant.you': "Ty",
package/src/i18n/pt.ts CHANGED
@@ -496,6 +496,13 @@ export const pt: Record<MessageKey, string> = {
496
496
  'chart.no_range': "Um gráfico de intervalo precisa de uma coluna de rótulos e duas colunas numéricas: mínimo e máximo.",
497
497
  'chart.no_boxplot': "Um diagrama de caixa precisa de uma coluna de rótulos e uma coluna numérica (uma linha por valor) ou cinco: mínimo, primeiro quartil, mediana, terceiro quartil e máximo.",
498
498
  'chart.no_stock': "Um gráfico de ações precisa de uma coluna de rótulos (a data) e quatro colunas numéricas: abertura, máxima, mínima e fecho.",
499
+ 'chart.no_tree': "Um treemap ou sunburst precisa de uma coluna de rótulos e de uma coluna numérica (opcionalmente as colunas node_id e parent_id para uma hierarquia).",
500
+ 'chart.no_timeline': "Uma linha do tempo precisa de uma coluna de rótulos e de uma data de início (uma data de fim e uma coluna series são opcionais).",
501
+ 'chart.total': "Total",
502
+ 'chart.gantt_hint': "Arraste uma tarefa para a mover, ou o seu fim para mudar a duração. Com o teclado: as setas movem uma tarefa, Shift com as setas muda o fim, Enter guarda, Escape cancela.",
503
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
504
+ 'chart.gantt_cannot_move': "Esta tarefa não pode ser movida.",
505
+ 'chart.gantt_bad_dates': "Escolha um início e um fim igual ou posterior ao início.",
499
506
  'chart.min': "Mínimo",
500
507
  'chart.q1': "Primeiro quartil",
501
508
  'chart.median': "Mediana",
@@ -652,6 +659,13 @@ export const pt: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "Dê um nome ao relatório.",
653
660
  'reporter.not_found': "Esse relatório não está disponível.",
654
661
  'reporter.unknown_source': "Esta fonte de dados já não está disponível.",
662
+ 'reporter.welcome_title': "Crie os seus próprios relatórios",
663
+ 'reporter.welcome_text': "Escolha os dados para começar um relatório: colunas, filtros, agrupamentos com totais e um gráfico; depois guarde-o ou partilhe-o.",
664
+ 'reporter.columns_count': "{n} colunas",
665
+ 'reporter.search': "Pesquisar relatórios",
666
+ 'reporter.changed': "alterado em {date}",
667
+ 'reporter.no_match': "Nenhum relatório corresponde à pesquisa.",
668
+ 'reporter.chart_tag': "gráfico",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "Conversa",
657
671
  'assistant.you': "Você",
package/src/i18n/ru.ts CHANGED
@@ -496,6 +496,13 @@ export const ru: Record<MessageKey, string> = {
496
496
  'chart.no_range': "Для диаграммы диапазонов нужны столбец подписей и два числовых столбца: минимум и максимум.",
497
497
  'chart.no_boxplot': "Для диаграммы «ящик с усами» нужны столбец подписей и либо один числовой столбец (строка на значение), либо пять: минимум, первый квартиль, медиана, третий квартиль и максимум.",
498
498
  'chart.no_stock': "Для биржевой диаграммы нужны столбец подписей (дата) и четыре числовых столбца: открытие, максимум, минимум и закрытие.",
499
+ 'chart.no_tree': "Для древовидной карты или диаграммы sunburst нужны столбец подписей и числовой столбец (для иерархии — также столбцы node_id и parent_id).",
500
+ 'chart.no_timeline': "Для временной шкалы нужны столбец подписей и дата начала (дата окончания и столбец series необязательны).",
501
+ 'chart.total': "Итого",
502
+ 'chart.gantt_hint': "Перетащите задачу, чтобы переместить её, или её конец, чтобы изменить длительность. С клавиатуры: стрелки перемещают задачу, Shift со стрелками меняет конец, Enter сохраняет, Escape отменяет.",
503
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
504
+ 'chart.gantt_cannot_move': "Эту задачу нельзя переместить.",
505
+ 'chart.gantt_bad_dates': "Выберите начало и конец не раньше начала.",
499
506
  'chart.min': "Минимум",
500
507
  'chart.q1': "Первый квартиль",
501
508
  'chart.median': "Медиана",
@@ -652,6 +659,13 @@ export const ru: Record<MessageKey, string> = {
652
659
  'reporter.name_required': "Дайте отчёту название.",
653
660
  'reporter.not_found': "Этот отчёт недоступен.",
654
661
  'reporter.unknown_source': "Этот источник данных больше недоступен.",
662
+ 'reporter.welcome_title': "Создавайте свои отчёты",
663
+ 'reporter.welcome_text': "Выберите данные для нового отчёта: столбцы, фильтры, группировку с итогами и диаграмму, затем сохраните его или поделитесь им.",
664
+ 'reporter.columns_count': "Столбцов: {n}",
665
+ 'reporter.search': "Поиск отчётов",
666
+ 'reporter.changed': "изменён {date}",
667
+ 'reporter.no_match': "Нет отчётов, подходящих под поиск.",
668
+ 'reporter.chart_tag': "диаграмма",
655
669
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
670
  'assistant.conversation': "Беседа",
657
671
  'assistant.you': "Вы",