feat(pitch-deck): translate financial plan row labels when lang=en
Build pitch-deck / build-push-deploy (push) Successful in 2m0s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 47s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 32s
Build pitch-deck / build-push-deploy (push) Successful in 2m0s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 47s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 32s
- Add ROW_LABEL_MAP (DE→EN) covering GuV, Liquidität, Kunden, Betriebliche Aufwendungen rows - Add FORMULA_TOOLTIPS_EN with English tooltip text for all formula-driven rows - Add MONTH_LABELS_EN (Mrz→Mar, Mai→May, Okt→Oct) - LabelWithTooltip now accepts `de` flag, translates display text and tooltip accordingly - Month column headers switch between DE/EN month abbreviations - Falls back to original German label for any row not in the map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,19 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SheetRow, MONTH_LABELS, FORMULA_TOOLTIPS,
|
SheetRow, MONTH_LABELS, MONTH_LABELS_EN, FORMULA_TOOLTIPS, FORMULA_TOOLTIPS_EN, ROW_LABEL_MAP,
|
||||||
getLabel, getValues, formatCell,
|
getLabel, getValues, formatCell,
|
||||||
} from './FinanzplanSlide.helpers'
|
} from './FinanzplanSlide.helpers'
|
||||||
|
|
||||||
/* ── Tooltip for formula-linked labels ── */
|
/* ── Tooltip for formula-linked labels ── */
|
||||||
|
|
||||||
function LabelWithTooltip({ label }: { label: string }) {
|
function LabelWithTooltip({ label, de }: { label: string; de: boolean }) {
|
||||||
const tooltip = FORMULA_TOOLTIPS[label]
|
const displayLabel = de ? label : (ROW_LABEL_MAP[label] ?? label)
|
||||||
if (!tooltip) return <span>{label}</span>
|
const tooltip = de ? FORMULA_TOOLTIPS[label] : (FORMULA_TOOLTIPS_EN[label] ?? FORMULA_TOOLTIPS[label])
|
||||||
|
if (!tooltip) return <span>{displayLabel}</span>
|
||||||
return (
|
return (
|
||||||
<span className="group relative cursor-help">
|
<span className="group relative cursor-help">
|
||||||
{label}
|
{displayLabel}
|
||||||
<span className="invisible group-hover:visible absolute left-0 top-full mt-1 z-50 bg-slate-800 border border-white/10 text-[9px] text-white/70 px-2 py-1 rounded shadow-lg whitespace-nowrap">
|
<span className="invisible group-hover:visible absolute left-0 top-full mt-1 z-50 bg-slate-800 border border-white/10 text-[9px] text-white/70 px-2 py-1 rounded shadow-lg whitespace-nowrap">
|
||||||
{tooltip}
|
{tooltip}
|
||||||
</span>
|
</span>
|
||||||
@@ -51,7 +52,7 @@ export function GuvTable({ rows, de }: GuvTableProps) {
|
|||||||
return (
|
return (
|
||||||
<tr key={row.id} className={`${isMajorSum ? 'border-t-2 border-t-white/20 border-b border-b-white/[0.05] bg-white/[0.05]' : isMinorSum ? 'border-t border-t-white/10 border-b border-b-white/[0.03] bg-white/[0.03]' : 'border-b border-white/[0.03]'} hover:bg-white/[0.02]`}>
|
<tr key={row.id} className={`${isMajorSum ? 'border-t-2 border-t-white/20 border-b border-b-white/[0.05] bg-white/[0.05]' : isMinorSum ? 'border-t border-t-white/10 border-b border-b-white/[0.03] bg-white/[0.03]' : 'border-b border-white/[0.03]'} hover:bg-white/[0.02]`}>
|
||||||
<td className={`py-1.5 px-2 sticky left-0 bg-slate-900/90 backdrop-blur ${isMajorSum ? 'font-bold text-white text-xs' : isMinorSum ? 'font-semibold text-white/80' : 'text-white/60'}`}>
|
<td className={`py-1.5 px-2 sticky left-0 bg-slate-900/90 backdrop-blur ${isMajorSum ? 'font-bold text-white text-xs' : isMinorSum ? 'font-semibold text-white/80' : 'text-white/60'}`}>
|
||||||
<LabelWithTooltip label={label} />
|
<LabelWithTooltip label={label} de={de} />
|
||||||
</td>
|
</td>
|
||||||
{[2026, 2027, 2028, 2029, 2030].map(y => {
|
{[2026, 2027, 2028, 2029, 2030].map(y => {
|
||||||
const v = values[`y${y}`] || 0
|
const v = values[`y${y}`] || 0
|
||||||
@@ -195,7 +196,7 @@ export function MonthlyGrid({ rows, activeSheet, de, yearOffset, openCats, toggl
|
|||||||
<th className="text-right py-1.5 px-2 text-white/60 font-medium min-w-[70px]">
|
<th className="text-right py-1.5 px-2 text-white/60 font-medium min-w-[70px]">
|
||||||
{currentYear}
|
{currentYear}
|
||||||
</th>
|
</th>
|
||||||
{MONTH_LABELS.map((label, idx) => (
|
{(de ? MONTH_LABELS : MONTH_LABELS_EN).map((label, idx) => (
|
||||||
<th key={idx} className="text-right py-1.5 px-1.5 text-white/50 font-normal min-w-[55px]">
|
<th key={idx} className="text-right py-1.5 px-1.5 text-white/50 font-normal min-w-[55px]">
|
||||||
{label}
|
{label}
|
||||||
</th>
|
</th>
|
||||||
@@ -241,7 +242,7 @@ export function MonthlyGrid({ rows, activeSheet, de, yearOffset, openCats, toggl
|
|||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{isCatHeader && <span className="text-[10px] text-indigo-400 w-3 shrink-0">{isCatOpen ? '▾' : '▸'}</span>}
|
{isCatHeader && <span className="text-[10px] text-indigo-400 w-3 shrink-0">{isCatOpen ? '▾' : '▸'}</span>}
|
||||||
{isEditable && <span className="w-1 h-1 rounded-full bg-indigo-400 flex-shrink-0" />}
|
{isEditable && <span className="w-1 h-1 rounded-full bg-indigo-400 flex-shrink-0" />}
|
||||||
<span className="truncate"><LabelWithTooltip label={label} /></span>
|
<span className="truncate"><LabelWithTooltip label={label} de={de} /></span>
|
||||||
{row.position && <span className="text-white/50 ml-1">({row.position})</span>}
|
{row.position && <span className="text-white/50 ml-1">({row.position})</span>}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -37,10 +37,86 @@ export const MONTH_LABELS = [
|
|||||||
'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez',
|
'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const MONTH_LABELS_EN = [
|
||||||
|
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||||
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
|
||||||
|
]
|
||||||
|
|
||||||
export function getLabel(row: SheetRow): string {
|
export function getLabel(row: SheetRow): string {
|
||||||
return row.row_label || row.person_name || row.item_name || '—'
|
return row.row_label || row.person_name || row.item_name || '—'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// German → English row label translations
|
||||||
|
export const ROW_LABEL_MAP: Record<string, string> = {
|
||||||
|
// GuV / P&L
|
||||||
|
'Umsatzerlöse': 'Revenue',
|
||||||
|
'Materialaufwand': 'Cost of Materials',
|
||||||
|
'Material-/Wareneinsatz': 'Materials / COGS',
|
||||||
|
'Fremdleistungen': 'External Services',
|
||||||
|
'Rohertrag': 'Gross Profit',
|
||||||
|
'Rohergebnis': 'Gross Result',
|
||||||
|
'Gesamtleistung': 'Total Output',
|
||||||
|
'Personalkosten': 'Personnel Costs',
|
||||||
|
'Abschreibungen': 'Depreciation',
|
||||||
|
'Betriebliche Aufwendungen': 'Operating Expenses',
|
||||||
|
'Sonstige betriebliche Aufwendungen': 'Other Operating Expenses',
|
||||||
|
'EBIT': 'EBIT',
|
||||||
|
'Betriebsergebnis': 'Operating Result',
|
||||||
|
'Zinsergebnis': 'Net Interest',
|
||||||
|
'Zinsaufwand': 'Interest Expense',
|
||||||
|
'Zinsertrag': 'Interest Income',
|
||||||
|
'Ergebnis vor Steuern': 'Earnings Before Tax',
|
||||||
|
'EBT': 'EBT',
|
||||||
|
'Gewerbesteuer': 'Trade Tax',
|
||||||
|
'Körperschaftsteuer': 'Corporate Tax',
|
||||||
|
'Steuern gesamt': 'Total Taxes',
|
||||||
|
'Steuern': 'Taxes',
|
||||||
|
'Jahresüberschuss': 'Net Income',
|
||||||
|
'Jahresfehlbetrag': 'Net Loss',
|
||||||
|
'Ergebnis nach Steuern': 'Net Result',
|
||||||
|
// Liquidität
|
||||||
|
'Einzahlungen': 'Cash Inflows',
|
||||||
|
'Summe Einzahlungen': 'Total Inflows',
|
||||||
|
'Auszahlungen': 'Cash Outflows',
|
||||||
|
'Summe Auszahlungen': 'Total Outflows',
|
||||||
|
'Überschuss/Fehlbetrag': 'Surplus / Deficit',
|
||||||
|
'ÜBERSCHUSS': 'SURPLUS',
|
||||||
|
'FEHLBETRAG': 'DEFICIT',
|
||||||
|
'ÜBERSCHUSS/FEHLBETRAG': 'SURPLUS / DEFICIT',
|
||||||
|
'Kontostand': 'Account Balance',
|
||||||
|
'Kontostand (Anfang)': 'Opening Balance',
|
||||||
|
'Kontostand (Ende)': 'Closing Balance',
|
||||||
|
'LIQUIDITÄT': 'LIQUIDITY',
|
||||||
|
'LIQUIDITAET': 'LIQUIDITY',
|
||||||
|
'Kredittilgung': 'Loan Repayment',
|
||||||
|
'Zins- und Tilgungszahlung': 'Interest & Principal',
|
||||||
|
'Investitionen': 'Investments',
|
||||||
|
'Sonstige Erträge': 'Other Income',
|
||||||
|
// Kunden
|
||||||
|
'Neukunden': 'New Customers',
|
||||||
|
'Bestandskunden': 'Existing Customers',
|
||||||
|
'Bestandskunden gesamt': 'Total Existing Customers',
|
||||||
|
'Anzahl Kunden': 'Customer Count',
|
||||||
|
'GESAMT': 'TOTAL',
|
||||||
|
'GESAMTUMSATZ': 'TOTAL REVENUE',
|
||||||
|
// Betriebliche Aufwendungen
|
||||||
|
'Fort-/Weiterbildungskosten (F)': 'Training & Development (F)',
|
||||||
|
'Fahrzeugkosten (F)': 'Vehicle Costs (F)',
|
||||||
|
'KFZ-Steuern (F)': 'Vehicle Tax (F)',
|
||||||
|
'KFZ-Versicherung (F)': 'Vehicle Insurance (F)',
|
||||||
|
'Reisekosten (F)': 'Travel Expenses (F)',
|
||||||
|
'Bewirtungskosten (F)': 'Entertainment Costs (F)',
|
||||||
|
'Internet/Mobilfunk (F)': 'Internet / Mobile (F)',
|
||||||
|
'Cloud-Hosting (SysEleven/Hetzner)': 'Cloud Hosting (SysEleven/Hetzner)',
|
||||||
|
'Berufsgenossenschaft (F)': 'Employers\' Liability Insurance (F)',
|
||||||
|
'Allgemeine Marketingkosten (F)': 'General Marketing Costs (F)',
|
||||||
|
'Gewerbesteuer (F)': 'Trade Tax (F)',
|
||||||
|
'Summe sonstige Aufwendungen': 'Total Other Expenses',
|
||||||
|
'SUMME Betriebliche Aufwendungen': 'TOTAL Operating Expenses',
|
||||||
|
'SUMME': 'TOTAL',
|
||||||
|
'Summe': 'Total',
|
||||||
|
}
|
||||||
|
|
||||||
export const FORMULA_TOOLTIPS: Record<string, string> = {
|
export const FORMULA_TOOLTIPS: Record<string, string> = {
|
||||||
'Fort-/Weiterbildungskosten (F)': 'Mitarbeiter (ohne Gründer) × 300 EUR/Mon',
|
'Fort-/Weiterbildungskosten (F)': 'Mitarbeiter (ohne Gründer) × 300 EUR/Mon',
|
||||||
'Fahrzeugkosten (F)': 'Mitarbeiter (ohne Gründer) × 200 EUR/Mon',
|
'Fahrzeugkosten (F)': 'Mitarbeiter (ohne Gründer) × 200 EUR/Mon',
|
||||||
@@ -57,6 +133,22 @@ export const FORMULA_TOOLTIPS: Record<string, string> = {
|
|||||||
'Abschreibungen': 'Summe AfA aus Tab Investitionen',
|
'Abschreibungen': 'Summe AfA aus Tab Investitionen',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const FORMULA_TOOLTIPS_EN: Record<string, string> = {
|
||||||
|
'Fort-/Weiterbildungskosten (F)': 'Employees (excl. founders) × €300/month',
|
||||||
|
'Fahrzeugkosten (F)': 'Employees (excl. founders) × €200/month',
|
||||||
|
'KFZ-Steuern (F)': 'Employees (excl. founders) × €25/month',
|
||||||
|
'KFZ-Versicherung (F)': 'Employees (excl. founders) × €150/month',
|
||||||
|
'Reisekosten (F)': 'Total headcount × €75/month',
|
||||||
|
'Bewirtungskosten (F)': 'Existing customers × €50/month',
|
||||||
|
'Internet/Mobilfunk (F)': 'Total headcount × €50/month',
|
||||||
|
'Cloud-Hosting (SysEleven/Hetzner)': '€1,500 base + (customers − 10) × €100 (first 10 included)',
|
||||||
|
'Berufsgenossenschaft (F)': '0.5% of gross payroll (VBG IT/Office)',
|
||||||
|
'Allgemeine Marketingkosten (F)': '8% of revenue (2026–2028), 10% from 2029',
|
||||||
|
'Gewerbesteuer (F)': '12.25% of profit (rate 3.5% × multiplier 350%, only when profitable)',
|
||||||
|
'Personalkosten': 'Sum from Personnel Costs tab',
|
||||||
|
'Abschreibungen': 'Sum of depreciation from Investments tab',
|
||||||
|
}
|
||||||
|
|
||||||
export function getValues(row: SheetRow): Record<string, number> {
|
export function getValues(row: SheetRow): Record<string, number> {
|
||||||
return row.values || row.values_total || row.values_brutto || (row as Record<string, unknown>).values_invest as Record<string, number> || {}
|
return row.values || row.values_total || row.values_brutto || (row as Record<string, unknown>).values_invest as Record<string, number> || {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user