Translates all German user-facing strings (command names, notices, settings, modal labels, template names/descriptions, error messages, status bar, and code comments) to English. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
180 lines
6.8 KiB
TypeScript
180 lines
6.8 KiB
TypeScript
import { SectionConfig, BuiltinFormat } from '../types';
|
|
import { toMarkdownTable } from '../viz/table-renderer';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Section → Markdown
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function formatSection(rows: Record<string, unknown>[], section: SectionConfig): string {
|
|
if (rows.length === 0) return `### ${section.heading}\n\n*No data.*\n`;
|
|
|
|
const heading = `### ${section.heading}\n\n`;
|
|
|
|
if (section.format.type === 'custom' && section.format.customTemplate) {
|
|
return heading + applyCustomTemplate(rows, section.format.customTemplate) + '\n';
|
|
}
|
|
|
|
const fmt = section.format.builtin;
|
|
if (!fmt) return heading + formatAsTable(rows) + '\n';
|
|
|
|
switch (fmt.name) {
|
|
case 'timeline':
|
|
return heading + formatAsTimeline(rows, fmt.showTimestamp, fmt.showPayload) + '\n';
|
|
case 'table':
|
|
return heading + formatAsTableWithColumns(rows, fmt.columns) + '\n';
|
|
case 'summary':
|
|
return heading + formatAsSummary(rows, fmt.metrics) + '\n';
|
|
case 'metric':
|
|
return heading + formatAsMetric(rows, fmt) + '\n';
|
|
case 'heatmap':
|
|
return heading + formatAsHeatmap(rows, fmt.labelField, fmt.valueField) + '\n';
|
|
default:
|
|
return heading + formatAsTable(rows) + '\n';
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Timeline
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function formatAsTimeline(rows: Record<string, unknown>[], showTs: boolean, showPayload: boolean): string {
|
|
const lines: string[] = [];
|
|
for (const row of rows) {
|
|
const parts: string[] = [];
|
|
if (showTs && row.timestamp != null) {
|
|
const ts = typeof row.timestamp === 'number'
|
|
? new Date(row.timestamp).toLocaleTimeString()
|
|
: String(row.timestamp);
|
|
parts.push(`**${ts}**`);
|
|
}
|
|
parts.push(String(row.type ?? ''));
|
|
if (row.source) parts.push(`\`${row.source}\``);
|
|
if (showPayload && row.payload) {
|
|
const p = typeof row.payload === 'string' ? row.payload : JSON.stringify(row.payload);
|
|
if (p !== '{}') parts.push(`— ${p}`);
|
|
}
|
|
lines.push(`- ${parts.join(' ')}`);
|
|
}
|
|
return lines.join('\n');
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Table
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function formatAsTable(rows: Record<string, unknown>[]): string {
|
|
const keys = Object.keys(rows[0]);
|
|
return toMarkdownTable(keys, rows);
|
|
}
|
|
|
|
function formatAsTableWithColumns(
|
|
rows: Record<string, unknown>[],
|
|
columns: { header: string; value: string; align?: string }[],
|
|
): string {
|
|
const headers = columns.map(c => c.header);
|
|
const mappedRows = rows.map(row => {
|
|
const mapped: Record<string, unknown> = {};
|
|
for (const col of columns) {
|
|
mapped[col.header] = row[col.value] ?? '';
|
|
}
|
|
return mapped;
|
|
});
|
|
return toMarkdownTable(headers, mappedRows);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Summary
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function formatAsSummary(
|
|
rows: Record<string, unknown>[],
|
|
metrics: { label: string; aggregate: string; field?: string }[],
|
|
): string {
|
|
const lines: string[] = [];
|
|
for (const m of metrics) {
|
|
const values = rows.map(r => Number(r[m.field ?? 'count'] ?? 0));
|
|
let result: number;
|
|
switch (m.aggregate) {
|
|
case 'sum': result = values.reduce((a, b) => a + b, 0); break;
|
|
case 'avg': result = values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0; break;
|
|
case 'min': result = values.length > 0 ? Math.min(...values) : 0; break;
|
|
case 'max': result = values.length > 0 ? Math.max(...values) : 0; break;
|
|
default: result = rows.length;
|
|
}
|
|
lines.push(`- **${m.label}**: ${result.toLocaleString()}`);
|
|
}
|
|
return lines.join('\n');
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Metric
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function formatAsMetric(
|
|
rows: Record<string, unknown>[],
|
|
fmt: { aggregate: string; field?: string },
|
|
): string {
|
|
const values = rows.map(r => Number(r[fmt.field ?? Object.keys(r).pop()!] ?? 0));
|
|
let result: number;
|
|
switch (fmt.aggregate) {
|
|
case 'sum': result = values.reduce((a, b) => a + b, 0); break;
|
|
case 'avg': result = values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0; break;
|
|
case 'min': result = values.length > 0 ? Math.min(...values) : 0; break;
|
|
case 'max': result = values.length > 0 ? Math.max(...values) : 0; break;
|
|
default: result = rows.length;
|
|
}
|
|
return `**${result.toLocaleString()}**`;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Heatmap
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function formatAsHeatmap(rows: Record<string, unknown>[], labelField: string, valueField: string): string {
|
|
const maxVal = Math.max(...rows.map(r => Number(r[valueField] ?? 0)), 1);
|
|
const lines: string[] = [];
|
|
for (const row of rows) {
|
|
const label = String(row[labelField] ?? '');
|
|
const val = Number(row[valueField] ?? 0);
|
|
const barLen = Math.round((val / maxVal) * 30);
|
|
const bar = '\u2588'.repeat(barLen);
|
|
lines.push(`\`${label.padEnd(12)}\` ${bar} ${val}`);
|
|
}
|
|
return lines.join('\n');
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Custom template
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function applyCustomTemplate(rows: Record<string, unknown>[], template: string): string {
|
|
return template.replace(/\{\{rows\}\}/g, JSON.stringify(rows, null, 2));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Frontmatter
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function buildFrontmatter(data: Record<string, string>): string {
|
|
if (Object.keys(data).length === 0) return '';
|
|
const lines = ['---'];
|
|
for (const [key, value] of Object.entries(data)) {
|
|
lines.push(`${key}: ${value}`);
|
|
}
|
|
lines.push('---', '');
|
|
return lines.join('\n');
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Date placeholders
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function resolvePlaceholders(pattern: string, context: { date?: Date; sessionId?: string }): string {
|
|
const d = context.date ?? new Date();
|
|
return pattern
|
|
.replace(/\{\{date\}\}/g, d.toISOString().substring(0, 10))
|
|
.replace(/\{\{year\}\}/g, String(d.getFullYear()))
|
|
.replace(/\{\{month\}\}/g, String(d.getMonth() + 1).padStart(2, '0'))
|
|
.replace(/\{\{day\}\}/g, String(d.getDate()).padStart(2, '0'))
|
|
.replace(/\{\{sessionId\}\}/g, context.sessionId ?? '');
|
|
}
|