pgkiln 0.40.0 → 0.42.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 (73) hide show
  1. package/db/migrations/095_rest_catalogs.sql +59 -0
  2. package/db/migrations/096_developer_email.sql +15 -0
  3. package/db/migrations/097_generated_app_quality.sql +911 -0
  4. package/db/migrations/098_app_activity.sql +37 -0
  5. package/docs/apex-feature-parity.md +15 -12
  6. package/docs/guide/03-builder.md +103 -19
  7. package/docs/guide/04-pages-and-regions.md +37 -4
  8. package/docs/guide/09-reference.md +6 -2
  9. package/docs/guide/11-from-apex.md +1 -1
  10. package/docs/guide/12-development.md +19 -2
  11. package/docs/guide/14-globalization.md +7 -3
  12. package/docs/guide/17-mobile.md +1 -1
  13. package/docs/guide/19-rest-data-sources.md +39 -1
  14. package/examples/hr/hr_59_gantt_editing.sql +21 -0
  15. package/examples/hr/hr_60_tree_charts.sql +46 -0
  16. package/package.json +1 -1
  17. package/public/app.css +219 -33
  18. package/public/app.js +206 -0
  19. package/public/builder.css +31 -3
  20. package/public/builder.js +31 -0
  21. package/public/sw.js +5 -2
  22. package/src/app.ts +6 -0
  23. package/src/builder/appfromfile.ts +5 -4
  24. package/src/builder/appsheets.ts +23 -9
  25. package/src/builder/appwizard.ts +215 -70
  26. package/src/builder/blueprints.ts +2 -1
  27. package/src/builder/components.ts +1 -1
  28. package/src/builder/find-workspace.ts +120 -0
  29. package/src/builder/home.ts +4 -32
  30. package/src/builder/mail.ts +39 -0
  31. package/src/builder/newapp.ts +182 -5
  32. package/src/builder/region-settings.ts +19 -1
  33. package/src/builder/rest-catalogs.ts +424 -0
  34. package/src/builder/routes.ts +36 -34
  35. package/src/builder/shared.ts +2 -0
  36. package/src/builder/workspace-transfer.ts +136 -0
  37. package/src/i18n/ar.ts +15 -0
  38. package/src/i18n/cs.ts +15 -0
  39. package/src/i18n/da.ts +15 -0
  40. package/src/i18n/de.ts +15 -0
  41. package/src/i18n/el.ts +15 -0
  42. package/src/i18n/es.ts +15 -0
  43. package/src/i18n/fi.ts +15 -0
  44. package/src/i18n/fr.ts +15 -0
  45. package/src/i18n/he.ts +15 -0
  46. package/src/i18n/it.ts +15 -0
  47. package/src/i18n/ja.ts +15 -0
  48. package/src/i18n/ko.ts +15 -0
  49. package/src/i18n/nb.ts +15 -0
  50. package/src/i18n/pl.ts +15 -0
  51. package/src/i18n/pt.ts +15 -0
  52. package/src/i18n/ru.ts +15 -0
  53. package/src/i18n/sv.ts +15 -0
  54. package/src/i18n/tr.ts +15 -0
  55. package/src/i18n/uk.ts +15 -0
  56. package/src/i18n/zh.ts +15 -0
  57. package/src/i18n.ts +30 -0
  58. package/src/icons.ts +2 -1
  59. package/src/layout.ts +2 -2
  60. package/src/restcatalog.ts +605 -0
  61. package/src/runtime/charts.ts +304 -15
  62. package/src/runtime/data-reporter.ts +48 -16
  63. package/src/runtime/devbar.ts +35 -14
  64. package/src/runtime/engine.ts +7 -4
  65. package/src/runtime/gantt-move.ts +63 -0
  66. package/src/runtime/locale.ts +2 -2
  67. package/src/runtime/regions.ts +28 -2
  68. package/src/runtime/render.ts +18 -4
  69. package/src/runtime/report.ts +15 -9
  70. package/src/runtime/routes.ts +39 -0
  71. package/src/runtime/styles.ts +5 -2
  72. package/src/websources.ts +11 -3
  73. 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
@@ -148,6 +148,7 @@ export const ar: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': "{n} نموذج في انتظار الإرسال",
149
149
  'pwa.send_now': "إرسال الآن",
150
150
  'pwa.discard': "تجاهل",
151
+ 'pwa.install': "تثبيت التطبيق",
151
152
  'pwa.status.waiting': "في الانتظار",
152
153
  'pwa.status.signin': "سجّل الدخول للإرسال",
153
154
  'pwa.status.invalid': "مرفوض: افتح الصفحة وصحّحه",
@@ -496,6 +497,13 @@ export const ar: Record<MessageKey, string> = {
496
497
  'chart.no_range': "يحتاج مخطط النطاق إلى عمود تسميات وعمودين رقميين: الأدنى والأعلى.",
497
498
  'chart.no_boxplot': "يحتاج المخطط الصندوقي إلى عمود تسميات وإما عمود رقمي واحد (صف لكل قيمة) أو خمسة: الحد الأدنى والربيع الأول والوسيط والربيع الثالث والحد الأقصى.",
498
499
  'chart.no_stock': "يحتاج مخطط الأسهم إلى عمود تسميات (التاريخ) وأربعة أعمدة رقمية: الافتتاح والأعلى والأدنى والإغلاق.",
500
+ 'chart.no_tree': "يحتاج مخطط الخريطة الشجرية أو المخطط الشمسي إلى عمود تسميات وعمود رقمي (واختياريًا عمودي node_id وparent_id للتسلسل الهرمي).",
501
+ 'chart.no_timeline': "يحتاج المخطط الزمني إلى عمود تسميات وتاريخ بداية (تاريخ النهاية وعمود series اختياريان).",
502
+ 'chart.total': "الإجمالي",
503
+ 'chart.gantt_hint': "اسحب مهمة لنقلها، أو نهايتها لتغيير مدتها. بلوحة المفاتيح: تنقل الأسهم المهمة، ويغيّر Shift مع الأسهم نهايتها، وEnter يحفظ، وEscape يلغي.",
504
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
505
+ 'chart.gantt_cannot_move': "لا يمكن نقل هذه المهمة.",
506
+ 'chart.gantt_bad_dates': "اختر بداية ونهاية في يوم البداية أو بعده.",
499
507
  'chart.min': "الحد الأدنى",
500
508
  'chart.q1': "الربيع الأول",
501
509
  'chart.median': "الوسيط",
@@ -652,6 +660,13 @@ export const ar: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "أعطِ التقرير اسمًا.",
653
661
  'reporter.not_found': "هذا التقرير غير متاح.",
654
662
  'reporter.unknown_source': "مصدر البيانات هذا لم يعد متاحًا.",
663
+ 'reporter.welcome_title': "أنشئ تقاريرك الخاصة",
664
+ 'reporter.welcome_text': "اختر البيانات لبدء تقرير: حدد الأعمدة، صفِّ، جمّع مع المجاميع، أضف مخططًا، ثم احفظه أو شاركه.",
665
+ 'reporter.columns_count': "{n} أعمدة",
666
+ 'reporter.search': "البحث في التقارير",
667
+ 'reporter.changed': "عُدِّل في {date}",
668
+ 'reporter.no_match': "لا توجد تقارير مطابقة للبحث.",
669
+ 'reporter.chart_tag': "مخطط",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "المحادثة",
657
672
  'assistant.you': "أنت",
package/src/i18n/cs.ts CHANGED
@@ -148,6 +148,7 @@ export const cs: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': "Formuláře čekající na odeslání: {n}",
149
149
  'pwa.send_now': "Odeslat hned",
150
150
  'pwa.discard': "Zahodit",
151
+ 'pwa.install': "Nainstalovat aplikaci",
151
152
  'pwa.status.waiting': "čeká",
152
153
  'pwa.status.signin': "pro odeslání se přihlaste",
153
154
  'pwa.status.invalid': "odmítnuto: otevřete stránku a opravte ho",
@@ -496,6 +497,13 @@ export const cs: Record<MessageKey, string> = {
496
497
  'chart.no_range': "Graf rozsahu potřebuje sloupec popisků a dva číselné sloupce: dolní a horní hodnotu.",
497
498
  '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
499
  'chart.no_stock': "Burzovní graf potřebuje sloupec popisků (datum) a čtyři číselné sloupce: otevření, maximum, minimum a uzavření.",
500
+ 'chart.no_tree': "Stromová mapa nebo sunburst potřebuje sloupec popisků a číselný sloupec (volitelně sloupce node_id a parent_id pro hierarchii).",
501
+ 'chart.no_timeline': "Časová osa potřebuje sloupec popisků a datum začátku (datum konce a sloupec series jsou volitelné).",
502
+ 'chart.total': "Celkem",
503
+ '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ší.",
504
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
505
+ 'chart.gantt_cannot_move': "Tento úkol nelze přesunout.",
506
+ 'chart.gantt_bad_dates': "Zvolte začátek a konec ve stejný den nebo po začátku.",
499
507
  'chart.min': "Minimum",
500
508
  'chart.q1': "První kvartil",
501
509
  'chart.median': "Medián",
@@ -652,6 +660,13 @@ export const cs: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "Pojmenujte sestavu.",
653
661
  'reporter.not_found': "Tato sestava není k dispozici.",
654
662
  'reporter.unknown_source': "Tento zdroj dat už není k dispozici.",
663
+ 'reporter.welcome_title': "Vytvořte si vlastní sestavy",
664
+ 'reporter.welcome_text': "Vyberte data pro novou sestavu: sloupce, filtry, seskupení se součty a graf, potom ji uložte nebo sdílejte.",
665
+ 'reporter.columns_count': "{n} sloupců",
666
+ 'reporter.search': "Hledat sestavy",
667
+ 'reporter.changed': "změněno {date}",
668
+ 'reporter.no_match': "Hledání neodpovídá žádná sestava.",
669
+ 'reporter.chart_tag': "graf",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "Konverzace",
657
672
  'assistant.you': "Vy",
package/src/i18n/da.ts CHANGED
@@ -148,6 +148,7 @@ export const da: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': "{n} formular(er) venter på at blive sendt",
149
149
  'pwa.send_now': "Send nu",
150
150
  'pwa.discard': "Kassér",
151
+ 'pwa.install': "Installér appen",
151
152
  'pwa.status.waiting': "venter",
152
153
  'pwa.status.signin': "log ind for at sende",
153
154
  'pwa.status.invalid': "afvist: åbn siden og ret den",
@@ -496,6 +497,13 @@ export const da: Record<MessageKey, string> = {
496
497
  'chart.no_range': "Et intervaldiagram kræver en etiketkolonne og to numeriske kolonner: lav og høj.",
497
498
  '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
499
  'chart.no_stock': "Et aktiediagram kræver en etiketkolonne (datoen) og fire numeriske kolonner: åbning, høj, lav og lukning.",
500
+ '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).",
501
+ 'chart.no_timeline': "En tidslinje kræver en etiketkolonne og en startdato (en slutdato og en series-kolonne er valgfrie).",
502
+ 'chart.total': "I alt",
503
+ '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.",
504
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
505
+ 'chart.gantt_cannot_move': "Denne opgave kan ikke flyttes.",
506
+ 'chart.gantt_bad_dates': "Vælg en start og en slutning på eller efter starten.",
499
507
  'chart.min': "Minimum",
500
508
  'chart.q1': "Første kvartil",
501
509
  'chart.median': "Median",
@@ -652,6 +660,13 @@ export const da: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "Giv rapporten et navn.",
653
661
  'reporter.not_found': "Den rapport er ikke tilgængelig.",
654
662
  'reporter.unknown_source': "Denne datakilde er ikke længere tilgængelig.",
663
+ 'reporter.welcome_title': "Lav dine egne rapporter",
664
+ '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.",
665
+ 'reporter.columns_count': "{n} kolonner",
666
+ 'reporter.search': "Søg i rapporter",
667
+ 'reporter.changed': "ændret {date}",
668
+ 'reporter.no_match': "Ingen rapporter matcher søgningen.",
669
+ 'reporter.chart_tag': "diagram",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "Samtale",
657
672
  'assistant.you': "Dig",
package/src/i18n/de.ts CHANGED
@@ -148,6 +148,7 @@ export const de: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': '{n} Formular(e) warten auf den Versand',
149
149
  'pwa.send_now': 'Jetzt senden',
150
150
  'pwa.discard': 'Verwerfen',
151
+ 'pwa.install': "App installieren",
151
152
  'pwa.status.waiting': 'wartet',
152
153
  'pwa.status.signin': 'zum Senden anmelden',
153
154
  'pwa.status.invalid': 'abgelehnt: Öffnen Sie die Seite und korrigieren Sie es',
@@ -457,6 +458,13 @@ export const de: Record<MessageKey, string> = {
457
458
  'chart.no_range': 'Ein Bereichsdiagramm benötigt eine Beschriftungsspalte und zwei numerische Spalten: Tief und Hoch.',
458
459
  '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
460
  'chart.no_stock': 'Ein Kursdiagramm benötigt eine Beschriftungsspalte (das Datum) und vier numerische Spalten: Eröffnung, Hoch, Tief und Schluss.',
461
+ '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).",
462
+ 'chart.no_timeline': "Eine Zeitleiste braucht eine Beschriftungsspalte und ein Startdatum (ein Enddatum und eine Spalte series sind optional).",
463
+ 'chart.total': "Gesamt",
464
+ '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.",
465
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
466
+ 'chart.gantt_cannot_move': "Diese Aufgabe kann nicht verschoben werden.",
467
+ 'chart.gantt_bad_dates': "Wählen Sie einen Beginn und ein Ende am oder nach dem Beginn.",
460
468
  'chart.min': 'Minimum',
461
469
  'chart.q1': 'Erstes Quartil',
462
470
  'chart.median': 'Median',
@@ -652,6 +660,13 @@ export const de: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "Geben Sie dem Bericht einen Namen.",
653
661
  'reporter.not_found': "Dieser Bericht ist nicht verfügbar.",
654
662
  'reporter.unknown_source': "Diese Datenquelle ist nicht mehr verfügbar.",
663
+ 'reporter.welcome_title': "Eigene Berichte erstellen",
664
+ '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.",
665
+ 'reporter.columns_count': "{n} Spalten",
666
+ 'reporter.search': "Berichte suchen",
667
+ 'reporter.changed': "geändert {date}",
668
+ 'reporter.no_match': "Keine Berichte gefunden.",
669
+ 'reporter.chart_tag': "Diagramm",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "Unterhaltung",
657
672
  'assistant.you': "Sie",
package/src/i18n/el.ts CHANGED
@@ -148,6 +148,7 @@ export const el: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': "{n} φόρμα(-ες) περιμένουν αποστολή",
149
149
  'pwa.send_now': "Αποστολή τώρα",
150
150
  'pwa.discard': "Απόρριψη",
151
+ 'pwa.install': "Εγκατάσταση εφαρμογής",
151
152
  'pwa.status.waiting': "σε αναμονή",
152
153
  'pwa.status.signin': "συνδεθείτε για αποστολή",
153
154
  'pwa.status.invalid': "απορρίφθηκε: ανοίξτε τη σελίδα και διορθώστε την",
@@ -496,6 +497,13 @@ export const el: Record<MessageKey, string> = {
496
497
  'chart.no_range': "Ένα γράφημα εύρους χρειάζεται μια στήλη ετικετών και δύο αριθμητικές στήλες: χαμηλή και υψηλή.",
497
498
  'chart.no_boxplot': "Ένα θηκόγραμμα χρειάζεται μια στήλη ετικετών και είτε μία αριθμητική στήλη (μία γραμμή ανά τιμή) είτε πέντε: ελάχιστο, πρώτο τεταρτημόριο, διάμεσο, τρίτο τεταρτημόριο και μέγιστο.",
498
499
  'chart.no_stock': "Ένα γράφημα μετοχών χρειάζεται μια στήλη ετικετών (την ημερομηνία) και τέσσερις αριθμητικές στήλες: άνοιγμα, υψηλό, χαμηλό και κλείσιμο.",
500
+ 'chart.no_tree': "Ένας χάρτης δέντρου ή ένα sunburst χρειάζεται στήλη ετικετών και αριθμητική στήλη (προαιρετικά στήλες node_id και parent_id για ιεραρχία).",
501
+ 'chart.no_timeline': "Ένα χρονολόγιο χρειάζεται στήλη ετικετών και ημερομηνία έναρξης (η ημερομηνία λήξης και μια στήλη series είναι προαιρετικές).",
502
+ 'chart.total': "Σύνολο",
503
+ 'chart.gantt_hint': "Σύρετε μια εργασία για να τη μετακινήσετε ή το τέλος της για να αλλάξετε τη διάρκεια. Με το πληκτρολόγιο: τα βέλη μετακινούν μια εργασία, Shift με τα βέλη αλλάζει το τέλος, Enter αποθηκεύει, Escape ακυρώνει.",
504
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
505
+ 'chart.gantt_cannot_move': "Αυτή η εργασία δεν μπορεί να μετακινηθεί.",
506
+ 'chart.gantt_bad_dates': "Επιλέξτε έναρξη και λήξη ίση ή μεταγενέστερη της έναρξης.",
499
507
  'chart.min': "Ελάχιστο",
500
508
  'chart.q1': "Πρώτο τεταρτημόριο",
501
509
  'chart.median': "Διάμεσος",
@@ -652,6 +660,13 @@ export const el: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "Δώστε όνομα στην αναφορά.",
653
661
  'reporter.not_found': "Αυτή η αναφορά δεν είναι διαθέσιμη.",
654
662
  'reporter.unknown_source': "Αυτή η πηγή δεδομένων δεν είναι πλέον διαθέσιμη.",
663
+ 'reporter.welcome_title': "Δημιουργήστε τις δικές σας αναφορές",
664
+ 'reporter.welcome_text': "Επιλέξτε τα δεδομένα για μια νέα αναφορά: στήλες, φίλτρα, ομαδοποίηση με σύνολα, ένα γράφημα, και αποθηκεύστε ή μοιραστείτε την.",
665
+ 'reporter.columns_count': "{n} στήλες",
666
+ 'reporter.search': "Αναζήτηση αναφορών",
667
+ 'reporter.changed': "άλλαξε {date}",
668
+ 'reporter.no_match': "Καμία αναφορά δεν ταιριάζει με την αναζήτηση.",
669
+ 'reporter.chart_tag': "γράφημα",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "Συνομιλία",
657
672
  'assistant.you': "Εσείς",
package/src/i18n/es.ts CHANGED
@@ -148,6 +148,7 @@ export const es: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': '{n} formulario(s) pendiente(s) de envío',
149
149
  'pwa.send_now': 'Enviar ahora',
150
150
  'pwa.discard': 'Descartar',
151
+ 'pwa.install': "Instalar la aplicación",
151
152
  'pwa.status.waiting': 'pendiente',
152
153
  'pwa.status.signin': 'inicie sesión para enviar',
153
154
  'pwa.status.invalid': 'rechazado: abra la página y corríjalo',
@@ -457,6 +458,13 @@ export const es: Record<MessageKey, string> = {
457
458
  'chart.no_range': 'Un gráfico de rango necesita una columna de etiquetas y dos columnas numéricas: mínimo y máximo.',
458
459
  '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
460
  '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.',
461
+ '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).",
462
+ '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).",
463
+ 'chart.total': "Total",
464
+ '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.",
465
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
466
+ 'chart.gantt_cannot_move': "Esta tarea no se puede mover.",
467
+ 'chart.gantt_bad_dates': "Elija un inicio y un final igual o posterior al inicio.",
460
468
  'chart.min': 'Mínimo',
461
469
  'chart.q1': 'Primer cuartil',
462
470
  'chart.median': 'Mediana',
@@ -652,6 +660,13 @@ export const es: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "Dé un nombre al informe.",
653
661
  'reporter.not_found': "Ese informe no está disponible.",
654
662
  'reporter.unknown_source': "Este origen de datos ya no está disponible.",
663
+ 'reporter.welcome_title': "Cree sus propios informes",
664
+ '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.",
665
+ 'reporter.columns_count': "{n} columnas",
666
+ 'reporter.search': "Buscar informes",
667
+ 'reporter.changed': "modificado el {date}",
668
+ 'reporter.no_match': "Ningún informe coincide con la búsqueda.",
669
+ 'reporter.chart_tag': "gráfico",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "Conversación",
657
672
  'assistant.you': "Usted",
package/src/i18n/fi.ts CHANGED
@@ -148,6 +148,7 @@ export const fi: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': "{n} lomaketta odottaa lähetystä",
149
149
  'pwa.send_now': "Lähetä nyt",
150
150
  'pwa.discard': "Hylkää",
151
+ 'pwa.install': "Asenna sovellus",
151
152
  'pwa.status.waiting': "odottaa",
152
153
  'pwa.status.signin': "kirjaudu lähettääksesi",
153
154
  'pwa.status.invalid': "hylätty: avaa sivu ja korjaa",
@@ -496,6 +497,13 @@ export const fi: Record<MessageKey, string> = {
496
497
  'chart.no_range': "Vaihteluvälikaavio tarvitsee otsikkosarakkeen ja kaksi numeerista saraketta: alin ja ylin.",
497
498
  '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
499
  'chart.no_stock': "Kurssikaavio tarvitsee otsikkosarakkeen (päivämäärä) ja neljä numeerista saraketta: avaus, ylin, alin ja päätös.",
500
+ 'chart.no_tree': "Puukartta tai sunburst tarvitsee nimikesarakkeen ja numeerisen sarakkeen (hierarkiaa varten myös sarakkeet node_id ja parent_id).",
501
+ 'chart.no_timeline': "Aikajana tarvitsee nimikesarakkeen ja alkupäivän (loppupäivä ja series-sarake ovat valinnaisia).",
502
+ 'chart.total': "Yhteensä",
503
+ '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.",
504
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
505
+ 'chart.gantt_cannot_move': "Tätä tehtävää ei voi siirtää.",
506
+ 'chart.gantt_bad_dates': "Valitse alku ja loppu, joka on alun jälkeen tai samana päivänä.",
499
507
  'chart.min': "Minimi",
500
508
  'chart.q1': "Ensimmäinen kvartiili",
501
509
  'chart.median': "Mediaani",
@@ -652,6 +660,13 @@ export const fi: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "Anna raportille nimi.",
653
661
  'reporter.not_found': "Raportti ei ole saatavilla.",
654
662
  'reporter.unknown_source': "Tämä tietolähde ei ole enää saatavilla.",
663
+ 'reporter.welcome_title': "Tee omia raportteja",
664
+ 'reporter.welcome_text': "Valitse tiedot uutta raporttia varten: sarakkeet, suodattimet, ryhmittely summineen ja kaavio, ja tallenna tai jaa raportti.",
665
+ 'reporter.columns_count': "{n} saraketta",
666
+ 'reporter.search': "Hae raportteja",
667
+ 'reporter.changed': "muutettu {date}",
668
+ 'reporter.no_match': "Haulla ei löytynyt raportteja.",
669
+ 'reporter.chart_tag': "kaavio",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "Keskustelu",
657
672
  'assistant.you': "Sinä",
package/src/i18n/fr.ts CHANGED
@@ -148,6 +148,7 @@ export const fr: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': '{n} formulaire(s) en attente d’envoi',
149
149
  'pwa.send_now': 'Envoyer maintenant',
150
150
  'pwa.discard': 'Abandonner',
151
+ 'pwa.install': "Installer l'application",
151
152
  'pwa.status.waiting': 'en attente',
152
153
  'pwa.status.signin': 'connectez-vous pour envoyer',
153
154
  'pwa.status.invalid': 'refusé : ouvrez la page et corrigez-le',
@@ -457,6 +458,13 @@ export const fr: Record<MessageKey, string> = {
457
458
  'chart.no_range': 'Un graphique de plage nécessite une colonne de libellés et deux colonnes numériques : bas et haut.',
458
459
  '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
460
  '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.',
461
+ '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).",
462
+ '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).",
463
+ 'chart.total': "Total",
464
+ '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.",
465
+ 'chart.gantt_moved': "{task} : {start} – {end}.",
466
+ 'chart.gantt_cannot_move': "Cette tâche ne peut pas être déplacée.",
467
+ 'chart.gantt_bad_dates': "Choisissez un début et une fin égale ou postérieure au début.",
460
468
  'chart.min': 'Minimum',
461
469
  'chart.q1': 'Premier quartile',
462
470
  'chart.median': 'Médiane',
@@ -652,6 +660,13 @@ export const fr: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "Donnez un nom au rapport.",
653
661
  'reporter.not_found': "Ce rapport n'est pas disponible.",
654
662
  'reporter.unknown_source': "Cette source de données n'est plus disponible.",
663
+ 'reporter.welcome_title': "Créez vos propres rapports",
664
+ 'reporter.welcome_text': "Choisissez les données d'un nouveau rapport : colonnes, filtres, regroupements avec totaux, un graphique, puis enregistrez-le ou partagez-le.",
665
+ 'reporter.columns_count': "{n} colonnes",
666
+ 'reporter.search': "Rechercher des rapports",
667
+ 'reporter.changed': "modifié le {date}",
668
+ 'reporter.no_match': "Aucun rapport ne correspond à la recherche.",
669
+ 'reporter.chart_tag': "graphique",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "Conversation",
657
672
  'assistant.you': "Vous",
package/src/i18n/he.ts CHANGED
@@ -148,6 +148,7 @@ export const he: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': "{n} טפסים ממתינים לשליחה",
149
149
  'pwa.send_now': "שליחה עכשיו",
150
150
  'pwa.discard': "ביטול",
151
+ 'pwa.install': "התקנת האפליקציה",
151
152
  'pwa.status.waiting': "ממתין",
152
153
  'pwa.status.signin': "יש להיכנס כדי לשלוח",
153
154
  'pwa.status.invalid': "נדחה: פתחו את הדף ותקנו",
@@ -496,6 +497,13 @@ export const he: Record<MessageKey, string> = {
496
497
  'chart.no_range': "תרשים טווח צריך עמודת תוויות ושתי עמודות מספריות: נמוך וגבוה.",
497
498
  'chart.no_boxplot': "תרשים קופסה צריך עמודת תוויות ועמודה מספרית אחת (שורה לכל ערך) או חמש: מינימום, רבעון ראשון, חציון, רבעון שלישי ומקסימום.",
498
499
  'chart.no_stock': "תרשים מניות צריך עמודת תוויות (התאריך) וארבע עמודות מספריות: פתיחה, גבוה, נמוך וסגירה.",
500
+ 'chart.no_tree': "מפת עץ או תרשים sunburst צריכים עמודת תוויות ועמודה מספרית (ולהיררכיה גם עמודות node_id ו-parent_id).",
501
+ 'chart.no_timeline': "ציר זמן צריך עמודת תוויות ותאריך התחלה (תאריך סיום ועמודת series הם אופציונליים).",
502
+ 'chart.total': "סה״כ",
503
+ 'chart.gantt_hint': "גררו משימה כדי להזיז אותה, או את סופה כדי לשנות את משכה. במקלדת: החצים מזיזים משימה, Shift עם החצים משנה את סופה, Enter שומר, Escape מבטל.",
504
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
505
+ 'chart.gantt_cannot_move': "לא ניתן להזיז משימה זו.",
506
+ 'chart.gantt_bad_dates': "בחרו התחלה וסיום שאינו לפני ההתחלה.",
499
507
  'chart.min': "מינימום",
500
508
  'chart.q1': "רבעון ראשון",
501
509
  'chart.median': "חציון",
@@ -652,6 +660,13 @@ export const he: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "תנו שם לדוח.",
653
661
  'reporter.not_found': "הדוח הזה אינו זמין.",
654
662
  'reporter.unknown_source': "מקור הנתונים הזה כבר אינו זמין.",
663
+ 'reporter.welcome_title': "בנו דוחות משלכם",
664
+ 'reporter.welcome_text': "בחרו את הנתונים כדי להתחיל דוח: עמודות, סינון, קיבוץ עם סיכומים ותרשים, ואז שמרו או שתפו אותו.",
665
+ 'reporter.columns_count': "{n} עמודות",
666
+ 'reporter.search': "חיפוש דוחות",
667
+ 'reporter.changed': "שונה ב-{date}",
668
+ 'reporter.no_match': "אין דוחות התואמים לחיפוש.",
669
+ 'reporter.chart_tag': "תרשים",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "שיחה",
657
672
  'assistant.you': "את/ה",
package/src/i18n/it.ts CHANGED
@@ -148,6 +148,7 @@ export const it: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': "{n} modulo/i in attesa di invio",
149
149
  'pwa.send_now': "Invia ora",
150
150
  'pwa.discard': "Scarta",
151
+ 'pwa.install': "Installa l'app",
151
152
  'pwa.status.waiting': "in attesa",
152
153
  'pwa.status.signin': "accedi per inviare",
153
154
  'pwa.status.invalid': "rifiutato: apri la pagina e correggilo",
@@ -496,6 +497,13 @@ export const it: Record<MessageKey, string> = {
496
497
  'chart.no_range': "Un grafico a intervalli richiede una colonna di etichette e due colonne numeriche: minimo e massimo.",
497
498
  '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
499
  'chart.no_stock': "Un grafico azionario richiede una colonna di etichette (la data) e quattro colonne numeriche: apertura, massimo, minimo e chiusura.",
500
+ '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).",
501
+ '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).",
502
+ 'chart.total': "Totale",
503
+ '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.",
504
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
505
+ 'chart.gantt_cannot_move': "Questa attività non può essere spostata.",
506
+ 'chart.gantt_bad_dates': "Scegli un inizio e una fine uguale o successiva all'inizio.",
499
507
  'chart.min': "Minimo",
500
508
  'chart.q1': "Primo quartile",
501
509
  'chart.median': "Mediana",
@@ -652,6 +660,13 @@ export const it: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "Dai un nome al report.",
653
661
  'reporter.not_found': "Quel report non è disponibile.",
654
662
  'reporter.unknown_source': "Questa fonte di dati non è più disponibile.",
663
+ 'reporter.welcome_title': "Crea i tuoi report",
664
+ 'reporter.welcome_text': "Scegli i dati per iniziare un report: colonne, filtri, raggruppamenti con totali e un grafico, poi salvalo o condividilo.",
665
+ 'reporter.columns_count': "{n} colonne",
666
+ 'reporter.search': "Cerca report",
667
+ 'reporter.changed': "modificato il {date}",
668
+ 'reporter.no_match': "Nessun report corrisponde alla ricerca.",
669
+ 'reporter.chart_tag': "grafico",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "Conversazione",
657
672
  'assistant.you': "Tu",
package/src/i18n/ja.ts CHANGED
@@ -148,6 +148,7 @@ export const ja: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': "送信待ちのフォーム: {n} 件",
149
149
  'pwa.send_now': "今すぐ送信",
150
150
  'pwa.discard': "破棄",
151
+ 'pwa.install': "アプリをインストール",
151
152
  'pwa.status.waiting': "待機中",
152
153
  'pwa.status.signin': "送信するにはサインイン",
153
154
  'pwa.status.invalid': "拒否: ページを開いて修正してください",
@@ -496,6 +497,13 @@ export const ja: Record<MessageKey, string> = {
496
497
  'chart.no_range': "範囲グラフには、ラベル列と 2 つの数値列(下限と上限)が必要です。",
497
498
  'chart.no_boxplot': "箱ひげ図には、ラベル列と、1 つの数値列(値ごとに 1 行)または 5 つの数値列(最小値、第 1 四分位数、中央値、第 3 四分位数、最大値)が必要です。",
498
499
  'chart.no_stock': "株価グラフには、ラベル列(日付)と 4 つの数値列(始値、高値、安値、終値)が必要です。",
500
+ 'chart.no_tree': "ツリーマップまたはサンバーストには、ラベル列と数値列が必要です(階層には node_id と parent_id 列も使えます)。",
501
+ 'chart.no_timeline': "タイムラインにはラベル列と開始日が必要です(終了日と series 列は任意です)。",
502
+ 'chart.total': "合計",
503
+ 'chart.gantt_hint': "タスクをドラッグして移動し、終わりをドラッグして期間を変更します。キーボード:矢印キーでタスクを移動、Shift と矢印キーで終わりを変更、Enter で保存、Escape で取り消します。",
504
+ 'chart.gantt_moved': "{task}:{start} – {end}。",
505
+ 'chart.gantt_cannot_move': "このタスクは移動できません。",
506
+ 'chart.gantt_bad_dates': "開始日と、開始日以降の終了日を選んでください。",
499
507
  'chart.min': "最小値",
500
508
  'chart.q1': "第 1 四分位数",
501
509
  'chart.median': "中央値",
@@ -652,6 +660,13 @@ export const ja: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "レポートに名前を付けてください。",
653
661
  'reporter.not_found': "そのレポートは利用できません。",
654
662
  'reporter.unknown_source': "このデータソースはもう利用できません。",
663
+ 'reporter.welcome_title': "自分のレポートを作成",
664
+ 'reporter.welcome_text': "データを選んでレポートを始めます。列の選択、フィルター、合計付きのグループ化、グラフの追加を行い、保存または共有できます。",
665
+ 'reporter.columns_count': "{n} 列",
666
+ 'reporter.search': "レポートを検索",
667
+ 'reporter.changed': "{date} に変更",
668
+ 'reporter.no_match': "検索に一致するレポートはありません。",
669
+ 'reporter.chart_tag': "グラフ",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "会話",
657
672
  'assistant.you': "あなた",
package/src/i18n/ko.ts CHANGED
@@ -148,6 +148,7 @@ export const ko: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': "전송 대기 중인 양식 {n}개",
149
149
  'pwa.send_now': "지금 보내기",
150
150
  'pwa.discard': "버리기",
151
+ 'pwa.install': "앱 설치",
151
152
  'pwa.status.waiting': "대기 중",
152
153
  'pwa.status.signin': "보내려면 로그인하세요",
153
154
  'pwa.status.invalid': "거부됨: 페이지를 열어 수정하세요",
@@ -496,6 +497,13 @@ export const ko: Record<MessageKey, string> = {
496
497
  'chart.no_range': "범위 차트에는 레이블 열과 두 개의 숫자 열(최저값과 최고값)이 필요합니다.",
497
498
  'chart.no_boxplot': "상자 그림에는 레이블 열과 숫자 열 하나(값마다 한 행) 또는 다섯 개(최솟값, 제1사분위수, 중앙값, 제3사분위수, 최댓값)가 필요합니다.",
498
499
  'chart.no_stock': "주가 차트에는 레이블 열(날짜)과 네 개의 숫자 열(시가, 고가, 저가, 종가)이 필요합니다.",
500
+ 'chart.no_tree': "트리맵 또는 선버스트에는 레이블 열과 숫자 열이 필요합니다(계층에는 node_id와 parent_id 열을 선택적으로 사용).",
501
+ 'chart.no_timeline': "타임라인에는 레이블 열과 시작 날짜가 필요합니다(종료 날짜와 series 열은 선택 사항).",
502
+ 'chart.total': "합계",
503
+ 'chart.gantt_hint': "작업을 끌어 이동하거나 끝을 끌어 기간을 바꾸세요. 키보드: 화살표 키로 작업을 이동하고, Shift와 화살표 키로 끝을 바꾸며, Enter로 저장, Escape로 취소합니다.",
504
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
505
+ 'chart.gantt_cannot_move': "이 작업은 이동할 수 없습니다.",
506
+ 'chart.gantt_bad_dates': "시작과, 시작과 같거나 이후인 끝을 선택하세요.",
499
507
  'chart.min': "최솟값",
500
508
  'chart.q1': "제1사분위수",
501
509
  'chart.median': "중앙값",
@@ -652,6 +660,13 @@ export const ko: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "보고서 이름을 입력하세요.",
653
661
  'reporter.not_found': "그 보고서는 사용할 수 없습니다.",
654
662
  'reporter.unknown_source': "이 데이터 원본은 더 이상 사용할 수 없습니다.",
663
+ 'reporter.welcome_title': "나만의 보고서 만들기",
664
+ 'reporter.welcome_text': "데이터를 선택해 보고서를 시작하세요. 열 선택, 필터, 합계가 있는 그룹, 차트를 추가한 뒤 저장하거나 공유할 수 있습니다.",
665
+ 'reporter.columns_count': "열 {n}개",
666
+ 'reporter.search': "보고서 검색",
667
+ 'reporter.changed': "{date} 변경됨",
668
+ 'reporter.no_match': "검색과 일치하는 보고서가 없습니다.",
669
+ 'reporter.chart_tag': "차트",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "대화",
657
672
  'assistant.you': "나",
package/src/i18n/nb.ts CHANGED
@@ -148,6 +148,7 @@ export const nb: Record<MessageKey, string> = {
148
148
  'pwa.queue_waiting': "{n} skjema(er) venter på å bli sendt",
149
149
  'pwa.send_now': "Send nå",
150
150
  'pwa.discard': "Forkast",
151
+ 'pwa.install': "Installer appen",
151
152
  'pwa.status.waiting': "venter",
152
153
  'pwa.status.signin': "logg inn for å sende",
153
154
  'pwa.status.invalid': "avvist: åpne siden og rett det",
@@ -496,6 +497,13 @@ export const nb: Record<MessageKey, string> = {
496
497
  'chart.no_range': "Et intervalldiagram trenger en etikettkolonne og to numeriske kolonner: lav og høy.",
497
498
  '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
499
  'chart.no_stock': "Et aksjediagram trenger en etikettkolonne (datoen) og fire numeriske kolonner: åpning, høy, lav og slutt.",
500
+ 'chart.no_tree': "Et trekart eller sunburst trenger en etikettkolonne og en numerisk kolonne (eventuelt kolonnene node_id og parent_id for et hierarki).",
501
+ 'chart.no_timeline': "En tidslinje trenger en etikettkolonne og en startdato (en sluttdato og en series-kolonne er valgfrie).",
502
+ 'chart.total': "Totalt",
503
+ '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.",
504
+ 'chart.gantt_moved': "{task}: {start} – {end}.",
505
+ 'chart.gantt_cannot_move': "Denne oppgaven kan ikke flyttes.",
506
+ 'chart.gantt_bad_dates': "Velg en start og en slutt på eller etter starten.",
499
507
  'chart.min': "Minimum",
500
508
  'chart.q1': "Første kvartil",
501
509
  'chart.median': "Median",
@@ -652,6 +660,13 @@ export const nb: Record<MessageKey, string> = {
652
660
  'reporter.name_required': "Gi rapporten et navn.",
653
661
  'reporter.not_found': "Den rapporten er ikke tilgjengelig.",
654
662
  'reporter.unknown_source': "Denne datakilden er ikke lenger tilgjengelig.",
663
+ 'reporter.welcome_title': "Lag dine egne rapporter",
664
+ 'reporter.welcome_text': "Velg data for å starte en rapport: velg kolonner, filtrer, grupper med summer, legg til et diagram, og lagre eller del den.",
665
+ 'reporter.columns_count': "{n} kolonner",
666
+ 'reporter.search': "Søk i rapporter",
667
+ 'reporter.changed': "endret {date}",
668
+ 'reporter.no_match': "Ingen rapporter samsvarer med søket.",
669
+ 'reporter.chart_tag': "diagram",
655
670
  // AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
656
671
  'assistant.conversation': "Samtale",
657
672
  'assistant.you': "Du",