Translate settings and purchasing UI text

This commit is contained in:
2026-06-11 10:08:37 +02:00
parent 2e7aeda684
commit ca6196234e
3 changed files with 181 additions and 95 deletions
@@ -37,13 +37,13 @@
</MudText> </MudText>
<div class="purchasing-hero-actions"> <div class="purchasing-hero-actions">
<MudChip T="string" Size="Size.Small" Color="@(_liveState.EkkoLoaded ? Color.Success : Color.Warning)" Variant="Variant.Outlined"> <MudChip T="string" Size="Size.Small" Color="@(_liveState.EkkoLoaded ? Color.Success : Color.Warning)" Variant="Variant.Outlined">
EKKO @(_liveState.EkkoLoaded ? "live" : "pending") EKKO @(_liveState.EkkoLoaded ? T("live", "live") : T("wartet", "pending"))
</MudChip> </MudChip>
<MudChip T="string" Size="Size.Small" Color="@(_liveState.EkpoLoaded ? Color.Success : Color.Warning)" Variant="Variant.Outlined"> <MudChip T="string" Size="Size.Small" Color="@(_liveState.EkpoLoaded ? Color.Success : Color.Warning)" Variant="Variant.Outlined">
EKPO @(_liveState.EkpoLoaded ? "live" : "pending") EKPO @(_liveState.EkpoLoaded ? T("live", "live") : T("wartet", "pending"))
</MudChip> </MudChip>
<MudChip T="string" Size="Size.Small" Color="@(_liveState.EketLoaded ? Color.Success : Color.Warning)" Variant="Variant.Outlined"> <MudChip T="string" Size="Size.Small" Color="@(_liveState.EketLoaded ? Color.Success : Color.Warning)" Variant="Variant.Outlined">
EKET @(_liveState.EketLoaded ? "live" : "pending") EKET @(_liveState.EketLoaded ? T("live", "live") : T("wartet", "pending"))
</MudChip> </MudChip>
</div> </div>
</div> </div>
@@ -371,7 +371,7 @@
"Full load builds the correct base. Delta then updates only changed purchase documents.") "Full load builds the correct base. Delta then updates only changed purchase documents.")
</MudText> </MudText>
</div> </div>
<MudChip T="string" Color="@ResolveRefreshStatusColor()" Variant="Variant.Outlined">@_refreshStatus.Status</MudChip> <MudChip T="string" Color="@ResolveRefreshStatusColor()" Variant="Variant.Outlined">@TranslateRefreshStatus(_refreshStatus.Status)</MudChip>
</MudStack> </MudStack>
<MudGrid Spacing="2" Class="mb-3"> <MudGrid Spacing="2" Class="mb-3">
<MudItem xs="12" sm="6" lg="3"> <MudItem xs="12" sm="6" lg="3">
@@ -399,7 +399,7 @@
<MudPaper Class="pa-3 purchasing-section-kpi" Outlined="true"> <MudPaper Class="pa-3 purchasing-section-kpi" Outlined="true">
<MudText Typo="Typo.caption" Class="purchasing-muted">@T("Letzter Stand", "Latest state")</MudText> <MudText Typo="Typo.caption" Class="purchasing-muted">@T("Letzter Stand", "Latest state")</MudText>
<MudText Typo="Typo.h6">@FormatRefreshDate(_refreshStatus.CompletedAtUtc)</MudText> <MudText Typo="Typo.h6">@FormatRefreshDate(_refreshStatus.CompletedAtUtc)</MudText>
<MudText Typo="Typo.caption">@_refreshStatus.Mode</MudText> <MudText Typo="Typo.caption">@TranslateRefreshMode(_refreshStatus.Mode)</MudText>
</MudPaper> </MudPaper>
</MudItem> </MudItem>
</MudGrid> </MudGrid>
@@ -415,7 +415,7 @@
{ {
<MudProgressLinear Indeterminate="true" Color="Color.Primary" Class="mb-2" /> <MudProgressLinear Indeterminate="true" Color="Color.Primary" Class="mb-2" />
} }
<MudText Typo="Typo.body2" Class="purchasing-muted">@_refreshStatus.Message</MudText> <MudText Typo="Typo.body2" Class="purchasing-muted">@TranslatePurchasingMessage(_refreshStatus.Message)</MudText>
</MudPaper> </MudPaper>
</MudItem> </MudItem>
} }
@@ -1534,7 +1534,66 @@
private string PurchasingStatusText private string PurchasingStatusText
=> _liveLoading => _liveLoading
? T("SAP-Einkaufsdaten werden geladen...", "Loading SAP purchasing data...") ? T("SAP-Einkaufsdaten werden geladen...", "Loading SAP purchasing data...")
: $"{_liveState.Message} {FormatLatestOrderDate()}"; : $"{TranslatePurchasingMessage(_liveState.Message)} {FormatLatestOrderDate()}";
private string TranslateRefreshStatus(string status)
=> status switch
{
"Success" => T("Erfolgreich", "Success"),
"Running" => T("Laeuft", "Running"),
"Error" => T("Fehler", "Error"),
"Empty" => T("Noch leer", "Empty"),
_ => string.IsNullOrWhiteSpace(status) ? "-" : status
};
private string TranslateRefreshMode(string mode)
=> mode switch
{
"Full" => T("Full Load", "Full load"),
"Delta" => T("Delta", "Delta"),
_ => string.IsNullOrWhiteSpace(mode) ? "-" : mode
};
private string TranslatePurchasingMessage(string message)
{
if (string.IsNullOrWhiteSpace(message))
return string.Empty;
if (message.Contains("SAP Einkaufsquelle ist noch nicht konfiguriert", StringComparison.OrdinalIgnoreCase))
return T("SAP Einkaufsquelle ist noch nicht konfiguriert.", "SAP purchasing source is not configured yet.");
if (message.Contains("SAP URL oder Zugangsdaten fehlen", StringComparison.OrdinalIgnoreCase))
return T("SAP URL oder Zugangsdaten fehlen.", "SAP URL or credentials are missing.");
if (message.Contains("SAP Einkaufsdaten inkl. EKPO/EKET geladen", StringComparison.OrdinalIgnoreCase))
return T("SAP Einkaufsdaten inkl. EKPO/EKET geladen.", "SAP purchasing data including EKPO/EKET loaded.");
if (message.Contains("SAP Einkaufsdaten inkl. EKPO geladen", StringComparison.OrdinalIgnoreCase))
return T("SAP Einkaufsdaten inkl. EKPO geladen; EKET liefert noch keine Termindaten.", "SAP purchasing data including EKPO loaded; EKET does not return schedule data yet.");
if (message.Contains("EKKO ist live geladen", StringComparison.OrdinalIgnoreCase))
return T("EKKO ist live geladen; EKPO/EKET liefern aktuell noch keine Positionsdaten.", "EKKO is loaded live; EKPO/EKET currently do not return item data.");
if (message.Contains("Noch kein Einkauf Full Load ausgefuehrt", StringComparison.OrdinalIgnoreCase))
return T("Noch kein Einkauf Full Load ausgefuehrt.", "No purchasing full load has been run yet.");
if (message.StartsWith("Full Load gestartet", StringComparison.OrdinalIgnoreCase))
return T("Full Load gestartet.", "Full load started.");
if (message.StartsWith("Delta gestartet", StringComparison.OrdinalIgnoreCase))
return T("Delta gestartet.", "Delta started.");
if (message.StartsWith("Full Load abgeschlossen", StringComparison.OrdinalIgnoreCase))
return message.Replace("Full Load abgeschlossen", T("Full Load abgeschlossen", "Full load completed"), StringComparison.OrdinalIgnoreCase);
if (message.StartsWith("Delta abgeschlossen", StringComparison.OrdinalIgnoreCase))
return message
.Replace("Delta abgeschlossen", T("Delta abgeschlossen", "Delta completed"), StringComparison.OrdinalIgnoreCase)
.Replace("geaenderte Belege", T("geaenderte Belege", "changed documents"), StringComparison.OrdinalIgnoreCase);
if (message.StartsWith("Einkauf Cache geladen fuer", StringComparison.OrdinalIgnoreCase))
return message
.Replace("Einkauf Cache geladen fuer", T("Einkauf Cache geladen fuer", "Purchasing cache loaded for"), StringComparison.OrdinalIgnoreCase)
.Replace(" bis ", $" {T("bis", "to")} ", StringComparison.OrdinalIgnoreCase);
if (message.StartsWith("SAP Einkauf konnte nicht geladen werden", StringComparison.OrdinalIgnoreCase))
return message.Replace("SAP Einkauf konnte nicht geladen werden", T("SAP Einkauf konnte nicht geladen werden", "SAP purchasing could not be loaded"), StringComparison.OrdinalIgnoreCase);
if (message.StartsWith("Full Load fehlgeschlagen", StringComparison.OrdinalIgnoreCase))
return message.Replace("Full Load fehlgeschlagen", T("Full Load fehlgeschlagen", "Full load failed"), StringComparison.OrdinalIgnoreCase);
if (message.StartsWith("Delta fehlgeschlagen", StringComparison.OrdinalIgnoreCase))
return message.Replace("Delta fehlgeschlagen", T("Delta fehlgeschlagen", "Delta failed"), StringComparison.OrdinalIgnoreCase);
return message;
}
private string FormatLatestOrderDate() private string FormatLatestOrderDate()
=> _liveState.LatestOrderDate.HasValue => _liveState.LatestOrderDate.HasValue
@@ -207,7 +207,7 @@ else
await RunAsync(async () => await RunAsync(async () =>
{ {
var result = await DataSourceService.TestConnectionAsync(_state); var result = await DataSourceService.TestConnectionAsync(_state);
Snackbar.Add(result.Message, result.Success ? Severity.Success : result.Warning ? Severity.Warning : Severity.Error); Snackbar.Add(TranslateConnectionMessage(result.Message), result.Success ? Severity.Success : result.Warning ? Severity.Warning : Severity.Error);
}); });
} }
@@ -246,6 +246,23 @@ else
private string T(string german, string english) => UiText.Text(german, english); private string T(string german, string english) => UiText.Text(german, english);
private static string Display(string? value) => string.IsNullOrWhiteSpace(value) ? "-" : value; private static string Display(string? value) => string.IsNullOrWhiteSpace(value) ? "-" : value;
private string TranslateConnectionMessage(string message)
{
if (string.IsNullOrWhiteSpace(message))
return string.Empty;
if (message.Contains("Keine SAP Service URL gepflegt", StringComparison.OrdinalIgnoreCase))
return T("Keine SAP Service URL gepflegt.", "No SAP service URL maintained.");
if (message.Contains("Keine SAP Gateway Zugangsdaten gepflegt", StringComparison.OrdinalIgnoreCase))
return T("Keine SAP Gateway Zugangsdaten gepflegt.", "No SAP Gateway credentials maintained.");
if (message.Contains("SAP OData Verbindung erfolgreich", StringComparison.OrdinalIgnoreCase))
return T("SAP OData Verbindung erfolgreich.", "SAP OData connection successful.");
if (message.StartsWith("SAP OData Verbindung fehlgeschlagen", StringComparison.OrdinalIgnoreCase))
return message.Replace("SAP OData Verbindung fehlgeschlagen", T("SAP OData Verbindung fehlgeschlagen", "SAP OData connection failed"), StringComparison.OrdinalIgnoreCase);
return message;
}
} }
<style> <style>
@@ -6,29 +6,31 @@
@inject ISettingsPageService SettingsPageActions @inject ISettingsPageService SettingsPageActions
@inject IJSRuntime JS @inject IJSRuntime JS
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@inject IUiTextService UiText
<PageTitle>Settings</PageTitle> <PageTitle>@T("Einstellungen", "Settings")</PageTitle>
<MudText Typo="Typo.h4" Class="mb-4">Settings</MudText> <MudText Typo="Typo.h4" Class="mb-4">@T("Einstellungen", "Settings")</MudText>
<MudText Typo="Typo.h5" Class="mb-2">Konfiguration Import/Export</MudText> <MudText Typo="Typo.h5" Class="mb-2">@T("Konfiguration Import/Export", "Import/export configuration")</MudText>
<MudPaper Class="pa-4 mb-6" Elevation="1"> <MudPaper Class="pa-4 mb-6" Elevation="1">
<MudGrid> <MudGrid>
<MudItem xs="12" md="6"> <MudItem xs="12" md="6">
<MudCheckBox @bind-Value="_includeSecretsInExport" Label="Mit Secrets exportieren" /> <MudCheckBox @bind-Value="_includeSecretsInExport" Label="@T("Mit Secrets exportieren", "Export with secrets")" />
<MudText Typo="Typo.caption"> <MudText Typo="Typo.caption">
Wenn deaktiviert, bleiben Passwörter und Secrets beim Export leer. Beim Import ohne Secrets werden bestehende Secrets auf dem Zielsystem beibehalten. @T("Wenn deaktiviert, bleiben Passwoerter und Secrets beim Export leer. Beim Import ohne Secrets werden bestehende Secrets auf dem Zielsystem beibehalten.",
"If disabled, passwords and secrets remain empty during export. When importing without secrets, existing secrets on the target system are kept.")
</MudText> </MudText>
</MudItem> </MudItem>
<MudItem xs="12" md="6"> <MudItem xs="12" md="6">
<MudStack Row Spacing="2"> <MudStack Row Spacing="2">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ExportConfiguration" <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ExportConfiguration"
StartIcon="@Icons.Material.Filled.Download" Disabled="_exportingConfig"> StartIcon="@Icons.Material.Filled.Download" Disabled="_exportingConfig">
@(_exportingConfig ? "Exportiere..." : "Konfiguration exportieren") @(_exportingConfig ? T("Exportiere...", "Exporting...") : T("Konfiguration exportieren", "Export configuration"))
</MudButton> </MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Warning" HtmlTag="label" <MudButton Variant="Variant.Outlined" Color="Color.Warning" HtmlTag="label"
StartIcon="@Icons.Material.Filled.UploadFile" Disabled="_importingConfig"> StartIcon="@Icons.Material.Filled.UploadFile" Disabled="_importingConfig">
@(_importingConfig ? "Importiere..." : "Konfiguration importieren") @(_importingConfig ? T("Importiere...", "Importing...") : T("Konfiguration importieren", "Import configuration"))
<InputFile OnChange="ImportConfiguration" accept=".json,application/json" style="display:none" /> <InputFile OnChange="ImportConfiguration" accept=".json,application/json" style="display:none" />
</MudButton> </MudButton>
</MudStack> </MudStack>
@@ -37,7 +39,7 @@
</MudPaper> </MudPaper>
@* SharePoint Config *@ @* SharePoint Config *@
<MudText Typo="Typo.h5" Class="mb-2">SharePoint Konfiguration</MudText> <MudText Typo="Typo.h5" Class="mb-2">@T("SharePoint Konfiguration", "SharePoint configuration")</MudText>
<MudPaper Class="pa-4 mb-6" Elevation="1"> <MudPaper Class="pa-4 mb-6" Elevation="1">
<MudGrid> <MudGrid>
<MudItem xs="12" md="6"> <MudItem xs="12" md="6">
@@ -49,7 +51,7 @@
<MudItem xs="12" md="6"> <MudItem xs="12" md="6">
<MudTextField @bind-Value="_spConfig.CentralExportFolder" <MudTextField @bind-Value="_spConfig.CentralExportFolder"
Label="Central Export Folder" Label="Central Export Folder"
HelperText="Optional. Wenn leer, wird weiterhin Export Folder/Alle verwendet." /> HelperText="@T("Optional. Wenn leer, wird weiterhin Export Folder/Alle verwendet.", "Optional. If empty, Export Folder/All is still used.")" />
</MudItem> </MudItem>
<MudItem xs="12" md="4"> <MudItem xs="12" md="4">
<MudTextField @bind-Value="_spConfig.TenantId" Label="Tenant ID" /> <MudTextField @bind-Value="_spConfig.TenantId" Label="Tenant ID" />
@@ -64,18 +66,18 @@
<MudStack Row Spacing="2"> <MudStack Row Spacing="2">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSharePoint" <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSharePoint"
StartIcon="@Icons.Material.Filled.Save"> StartIcon="@Icons.Material.Filled.Save">
Speichern @T("Speichern", "Save")
</MudButton> </MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Info" OnClick="TestSharePoint" <MudButton Variant="Variant.Outlined" Color="Color.Info" OnClick="TestSharePoint"
StartIcon="@Icons.Material.Filled.NetworkCheck" Disabled="_testingSp"> StartIcon="@Icons.Material.Filled.NetworkCheck" Disabled="_testingSp">
@if (_testingSp) @if (_testingSp)
{ {
<MudProgressCircular Size="Size.Small" Indeterminate Class="mr-2" /> <MudProgressCircular Size="Size.Small" Indeterminate Class="mr-2" />
@("Teste...") @T("Teste...", "Testing...")
} }
else else
{ {
@("SharePoint Verbindung testen") @T("SharePoint Verbindung testen", "Test SharePoint connection")
} }
</MudButton> </MudButton>
</MudStack> </MudStack>
@@ -84,7 +86,7 @@
{ {
<MudItem xs="12"> <MudItem xs="12">
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined" Class="mt-3"> <MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined" Class="mt-3">
<div><b>Test Preview</b></div> <div><b>@T("Testvorschau", "Test preview")</b></div>
<div style="white-space: pre-wrap">@_sharePointTestPreview</div> <div style="white-space: pre-wrap">@_sharePointTestPreview</div>
</MudAlert> </MudAlert>
</MudItem> </MudItem>
@@ -92,28 +94,29 @@
</MudGrid> </MudGrid>
</MudPaper> </MudPaper>
<MudText Typo="Typo.h5" Class="mb-2">Quellsysteme</MudText> <MudText Typo="Typo.h5" Class="mb-2">@T("Quellsysteme", "Source systems")</MudText>
<MudPaper Class="pa-4 mb-6" Elevation="1"> <MudPaper Class="pa-4 mb-6" Elevation="1">
<MudGrid> <MudGrid>
<MudItem xs="12"> <MudItem xs="12">
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined"> <MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined">
Diese Zugangsdaten werden pro Quellsystem als Standard verwendet. Ein Standort kann sie bei Bedarf mit eigenen Overrides überschreiben. @T("Diese Zugangsdaten werden pro Quellsystem als Standard verwendet. Ein Standort kann sie bei Bedarf mit eigenen Overrides ueberschreiben.",
"These credentials are used as defaults per source system. A site can override them if needed.")
</MudAlert> </MudAlert>
</MudItem> </MudItem>
<MudItem xs="12"> <MudItem xs="12">
<MudButton Variant="Variant.Outlined" Color="Color.Primary" OnClick="AddSourceSystem" <MudButton Variant="Variant.Outlined" Color="Color.Primary" OnClick="AddSourceSystem"
StartIcon="@Icons.Material.Filled.Add" Class="mb-3"> StartIcon="@Icons.Material.Filled.Add" Class="mb-3">
Quellsystem hinzufuegen @T("Quellsystem hinzufuegen", "Add source system")
</MudButton> </MudButton>
<MudTable Items="_sourceSystems" Dense Hover Striped Breakpoint="Breakpoint.Md"> <MudTable Items="_sourceSystems" Dense Hover Striped Breakpoint="Breakpoint.Md">
<HeaderContent> <HeaderContent>
<MudTh>Code</MudTh> <MudTh>Code</MudTh>
<MudTh>Name</MudTh> <MudTh>@T("Name", "Name")</MudTh>
<MudTh>Anschlussart</MudTh> <MudTh>@T("Anschlussart", "Connection type")</MudTh>
<MudTh>Zentrale URL</MudTh> <MudTh>@T("Zentrale URL", "Central URL")</MudTh>
<MudTh>User</MudTh> <MudTh>User</MudTh>
<MudTh>Aktiv</MudTh> <MudTh>@T("Aktiv", "Active")</MudTh>
<MudTh>Test</MudTh> <MudTh>@T("Test", "Test")</MudTh>
<MudTh></MudTh> <MudTh></MudTh>
</HeaderContent> </HeaderContent>
<RowTemplate> <RowTemplate>
@@ -138,7 +141,7 @@
<MudButton Variant="Variant.Outlined" Color="Color.Info" Size="Size.Small" <MudButton Variant="Variant.Outlined" Color="Color.Info" Size="Size.Small"
OnClick='@(() => TestCentralCredentials(context.Code))' OnClick='@(() => TestCentralCredentials(context.Code))'
Disabled='@_testingSystems.Contains(context.Code)'> Disabled='@_testingSystems.Contains(context.Code)'>
@(_testingSystems.Contains(context.Code) ? "Teste..." : "Testen") @(_testingSystems.Contains(context.Code) ? T("Teste...", "Testing...") : T("Testen", "Test"))
</MudButton> </MudButton>
} }
</MudTd> </MudTd>
@@ -154,7 +157,7 @@
<MudItem xs="12"> <MudItem xs="12">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSourceSystems" <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSourceSystems"
StartIcon="@Icons.Material.Filled.Save"> StartIcon="@Icons.Material.Filled.Save">
Quellsysteme speichern @T("Quellsysteme speichern", "Save source systems")
</MudButton> </MudButton>
</MudItem> </MudItem>
</MudGrid> </MudGrid>
@@ -162,12 +165,12 @@
<MudDialog @bind-Visible="_sourceSystemDialogVisible" Options="_sourceSystemDialogOptions"> <MudDialog @bind-Visible="_sourceSystemDialogVisible" Options="_sourceSystemDialogOptions">
<TitleContent> <TitleContent>
<MudText Typo="Typo.h6">@(_editingSourceSystem.Id == 0 ? "Quellsystem hinzufuegen" : "Quellsystem bearbeiten")</MudText> <MudText Typo="Typo.h6">@(_editingSourceSystem.Id == 0 ? T("Quellsystem hinzufuegen", "Add source system") : T("Quellsystem bearbeiten", "Edit source system"))</MudText>
</TitleContent> </TitleContent>
<DialogContent> <DialogContent>
<MudTextField @bind-Value="_editingSourceSystem.Code" Label="Code" Required /> <MudTextField @bind-Value="_editingSourceSystem.Code" Label="Code" Required />
<MudTextField @bind-Value="_editingSourceSystem.DisplayName" Label="Name" Required /> <MudTextField @bind-Value="_editingSourceSystem.DisplayName" Label="@T("Name", "Name")" Required />
<MudSelect T="string" @bind-Value="_editingSourceSystem.ConnectionKind" Label="Anschlussart" Required> <MudSelect T="string" @bind-Value="_editingSourceSystem.ConnectionKind" Label="@T("Anschlussart", "Connection type")" Required>
@foreach (var kind in SourceSystemConnectionKinds.All) @foreach (var kind in SourceSystemConnectionKinds.All)
{ {
<MudSelectItem Value="@kind">@GetConnectionKindLabel(kind)</MudSelectItem> <MudSelectItem Value="@kind">@GetConnectionKindLabel(kind)</MudSelectItem>
@@ -176,46 +179,47 @@
@if (UsesSapGateway(_editingSourceSystem)) @if (UsesSapGateway(_editingSourceSystem))
{ {
<MudTextField @bind-Value="_editingSourceSystem.CentralServiceUrl" Label="Zentrale SAP Service URL" <MudTextField @bind-Value="_editingSourceSystem.CentralServiceUrl" Label="Zentrale SAP Service URL"
HelperText="Zentrale Standard-URL fuer SAP Gateway. Ein Standort darf sie nur bei Bedarf ueberschreiben." /> HelperText="@T("Zentrale Standard-URL fuer SAP Gateway. Ein Standort darf sie nur bei Bedarf ueberschreiben.", "Central default URL for SAP Gateway. A site may override it only if needed.")" />
} }
<MudTextField @bind-Value="_editingSourceSystem.CentralUsername" Label="Zentraler Username" /> <MudTextField @bind-Value="_editingSourceSystem.CentralUsername" Label="@T("Zentraler Username", "Central username")" />
<MudTextField @bind-Value="_editingSourceSystem.CentralPassword" Label="Zentrales Passwort" InputType="InputType.Password" /> <MudTextField @bind-Value="_editingSourceSystem.CentralPassword" Label="@T("Zentrales Passwort", "Central password")" InputType="InputType.Password" />
<MudCheckBox @bind-Value="_editingSourceSystem.IsActive" Label="Aktiv" /> <MudCheckBox @bind-Value="_editingSourceSystem.IsActive" Label="@T("Aktiv", "Active")" />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<MudButton OnClick="CloseSourceSystemDialog">Abbrechen</MudButton> <MudButton OnClick="CloseSourceSystemDialog">@T("Abbrechen", "Cancel")</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSourceSystemEdit">Uebernehmen</MudButton> <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSourceSystemEdit">@T("Uebernehmen", "Apply")</MudButton>
</DialogActions> </DialogActions>
</MudDialog> </MudDialog>
<MudText Typo="Typo.h5" Class="mb-2">Wechselkurse</MudText> <MudText Typo="Typo.h5" Class="mb-2">@T("Wechselkurse", "Exchange rates")</MudText>
<MudPaper Class="pa-4 mb-6" Elevation="1"> <MudPaper Class="pa-4 mb-6" Elevation="1">
<MudText Typo="Typo.body2" Class="mb-3"> <MudText Typo="Typo.body2" Class="mb-3">
Diese Kurstabelle wird von der Transformation <b>ConvertCurrency</b> verwendet. Gleiche Waehrung rechnet automatisch mit Faktor 1. @((MarkupString)T("Diese Kurstabelle wird von der Transformation <b>ConvertCurrency</b> verwendet. Gleiche Waehrung rechnet automatisch mit Faktor 1.",
"This rate table is used by the <b>ConvertCurrency</b> transformation. Same-currency conversion automatically uses factor 1."))
</MudText> </MudText>
<MudStack Row Spacing="2" Class="mb-3"> <MudStack Row Spacing="2" Class="mb-3">
<MudButton Variant="Variant.Outlined" Color="Color.Primary" OnClick="AddExchangeRate" <MudButton Variant="Variant.Outlined" Color="Color.Primary" OnClick="AddExchangeRate"
StartIcon="@Icons.Material.Filled.Add"> StartIcon="@Icons.Material.Filled.Add">
Kurs hinzufuegen @T("Kurs hinzufuegen", "Add rate")
</MudButton> </MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Info" OnClick="RefreshEcbRates" <MudButton Variant="Variant.Outlined" Color="Color.Info" OnClick="RefreshEcbRates"
StartIcon="@Icons.Material.Filled.Refresh" Disabled="_refreshingExchangeRates"> StartIcon="@Icons.Material.Filled.Refresh" Disabled="_refreshingExchangeRates">
@(_refreshingExchangeRates ? "Aktualisiere ECB-Kurse..." : "Refresh Kurse") @(_refreshingExchangeRates ? T("Aktualisiere ECB-Kurse...", "Refreshing ECB rates...") : T("Refresh Kurse", "Refresh rates"))
</MudButton> </MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveExchangeRates" <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveExchangeRates"
StartIcon="@Icons.Material.Filled.Save"> StartIcon="@Icons.Material.Filled.Save">
Kurse speichern @T("Kurse speichern", "Save rates")
</MudButton> </MudButton>
</MudStack> </MudStack>
<MudTable Items="_exchangeRates" Hover="true" Breakpoint="Breakpoint.Md"> <MudTable Items="_exchangeRates" Hover="true" Breakpoint="Breakpoint.Md">
<HeaderContent> <HeaderContent>
<MudTh>Von</MudTh> <MudTh>@T("Von", "From")</MudTh>
<MudTh>Nach</MudTh> <MudTh>@T("Nach", "To")</MudTh>
<MudTh>Kurs</MudTh> <MudTh>@T("Kurs", "Rate")</MudTh>
<MudTh>Gueltig ab</MudTh> <MudTh>@T("Gueltig ab", "Valid from")</MudTh>
<MudTh>Gueltig bis</MudTh> <MudTh>@T("Gueltig bis", "Valid to")</MudTh>
<MudTh>Notiz</MudTh> <MudTh>@T("Notiz", "Note")</MudTh>
<MudTh>Aktiv</MudTh> <MudTh>@T("Aktiv", "Active")</MudTh>
<MudTh></MudTh> <MudTh></MudTh>
</HeaderContent> </HeaderContent>
<RowTemplate> <RowTemplate>
@@ -253,73 +257,77 @@
</MudPaper> </MudPaper>
@* Export Settings *@ @* Export Settings *@
<MudText Typo="Typo.h5" Class="mb-2">Export Einstellungen</MudText> <MudText Typo="Typo.h5" Class="mb-2">@T("Export Einstellungen", "Export settings")</MudText>
<MudPaper Class="pa-4 mb-6" Elevation="1"> <MudPaper Class="pa-4 mb-6" Elevation="1">
<MudGrid> <MudGrid>
<MudItem xs="12" md="4"> <MudItem xs="12" md="4">
<MudTextField @bind-Value="_exportSettings.DateFilter" Label="Datum-Filter (ab)" <MudTextField @bind-Value="_exportSettings.DateFilter" Label="@T("Datum-Filter (ab)", "Date filter from")"
HelperText="Format: yyyy-MM-dd" /> HelperText="@T("Format: yyyy-MM-dd", "Format: yyyy-MM-dd")" />
</MudItem> </MudItem>
<MudItem xs="12" md="2"> <MudItem xs="12" md="2">
<MudNumericField @bind-Value="_exportSettings.TimerHour" Label="Timer Stunde" Min="0" Max="23" /> <MudNumericField @bind-Value="_exportSettings.TimerHour" Label="@T("Timer Stunde", "Timer hour")" Min="0" Max="23" />
</MudItem> </MudItem>
<MudItem xs="12" md="2"> <MudItem xs="12" md="2">
<MudNumericField @bind-Value="_exportSettings.TimerMinute" Label="Timer Minute" Min="0" Max="59" /> <MudNumericField @bind-Value="_exportSettings.TimerMinute" Label="@T("Timer Minute", "Timer minute")" Min="0" Max="59" />
</MudItem> </MudItem>
<MudItem xs="12" md="4"> <MudItem xs="12" md="4">
<MudSwitch @bind-Value="_exportSettings.TimerEnabled" Label="Timer aktiviert" Color="Color.Primary" /> <MudSwitch @bind-Value="_exportSettings.TimerEnabled" Label="@T("Timer aktiviert", "Timer enabled")" Color="Color.Primary" />
</MudItem> </MudItem>
<MudItem xs="12" md="4"> <MudItem xs="12" md="4">
<MudSelect T="string" @bind-Value="_exportSettings.ExchangeRateDateField" <MudSelect T="string" @bind-Value="_exportSettings.ExchangeRateDateField"
Label="Wechselkurse anwenden auf" Label="@T("Wechselkurse anwenden auf", "Apply exchange rates to")"
HelperText="Datumsfeld fuer Kursgueltigkeit in Management-Analysen."> HelperText="@T("Datumsfeld fuer Kursgueltigkeit in Management-Analysen.", "Date field for rate validity in management analyses.")">
<MudSelectItem Value="@ExchangeRateDateFields.PostingDate">PostingDate / Buchungsdatum</MudSelectItem> <MudSelectItem Value="@ExchangeRateDateFields.PostingDate">@T("PostingDate / Buchungsdatum", "PostingDate / posting date")</MudSelectItem>
<MudSelectItem Value="@ExchangeRateDateFields.InvoiceDate">InvoiceDate / Rechnungsdatum</MudSelectItem> <MudSelectItem Value="@ExchangeRateDateFields.InvoiceDate">@T("InvoiceDate / Rechnungsdatum", "InvoiceDate / invoice date")</MudSelectItem>
<MudSelectItem Value="@ExchangeRateDateFields.ExtractionDate">ExtractionDate / Extraktionsdatum</MudSelectItem> <MudSelectItem Value="@ExchangeRateDateFields.ExtractionDate">@T("ExtractionDate / Extraktionsdatum", "ExtractionDate / extraction date")</MudSelectItem>
</MudSelect> </MudSelect>
</MudItem> </MudItem>
<MudItem xs="12" md="4"> <MudItem xs="12" md="4">
<MudSwitch @bind-Value="_exportSettings.DebugLoggingEnabled" Label="Debug Live-Logging" Color="Color.Warning" /> <MudSwitch @bind-Value="_exportSettings.DebugLoggingEnabled" Label="@T("Debug Live-Logging", "Debug live logging")" Color="Color.Warning" />
<MudText Typo="Typo.caption"> <MudText Typo="Typo.caption">
Schreibt zusätzliche technische Fortschrittsmeldungen für HANA- und SAP-Lesevorgänge ins Dashboard und in die Logs. @T("Schreibt zusaetzliche technische Fortschrittsmeldungen fuer HANA- und SAP-Lesevorgaenge ins Dashboard und in die Logs.",
"Writes additional technical progress messages for HANA and SAP reads to the dashboard and logs.")
</MudText> </MudText>
</MudItem> </MudItem>
<MudItem xs="12" md="6"> <MudItem xs="12" md="6">
<MudTextField @bind-Value="_exportSettings.LocalSiteExportFolder" Label="Lokaler Standardpfad Standort-Dateien" <MudTextField @bind-Value="_exportSettings.LocalSiteExportFolder" Label="@T("Lokaler Standardpfad Standort-Dateien", "Local default path for site files")"
HelperText="Wenn leer, wird ./output unter dem Programmverzeichnis verwendet." /> HelperText="@T("Wenn leer, wird ./output unter dem Programmverzeichnis verwendet.", "If empty, ./output under the application directory is used.")" />
</MudItem> </MudItem>
<MudItem xs="12" md="6"> <MudItem xs="12" md="6">
<MudTextField @bind-Value="_exportSettings.LocalConsolidatedExportFolder" Label="Lokaler Pfad Zentrale Datei" <MudTextField @bind-Value="_exportSettings.LocalConsolidatedExportFolder" Label="@T("Lokaler Pfad Zentrale Datei", "Local path for central file")"
HelperText="Optional. Wenn leer, wird der Standardpfad der Standort-Dateien verwendet." /> HelperText="@T("Optional. Wenn leer, wird der Standardpfad der Standort-Dateien verwendet.", "Optional. If empty, the default path for site files is used.")" />
</MudItem> </MudItem>
<MudItem xs="12"> <MudItem xs="12">
<div class="audit-csv-settings"> <div class="audit-csv-settings">
<div class="audit-csv-header"> <div class="audit-csv-header">
<MudIcon Icon="@Icons.Material.Filled.RuleFolder" Color="Color.Info" Size="Size.Medium" /> <MudIcon Icon="@Icons.Material.Filled.RuleFolder" Color="Color.Info" Size="Size.Medium" />
<div> <div>
<MudText Typo="Typo.h6">Audit-CSV / nachvollziehbarer Datenfluss</MudText> <MudText Typo="Typo.h6">@T("Audit-CSV / nachvollziehbarer Datenfluss", "Audit CSV / traceable data flow")</MudText>
<MudText Typo="Typo.body2"> <MudText Typo="Typo.body2">
Fuer Finance und Wirtschaftspruefung: lesbare Standort-CSV nach Mapping und Konvertierung, optional als Quelle fuer zentrale Auswertungen. @T("Fuer Finance und Wirtschaftspruefung: lesbare Standort-CSV nach Mapping und Konvertierung, optional als Quelle fuer zentrale Auswertungen.",
"For finance and auditors: readable site CSV after mapping and conversion, optionally as the source for central analyses.")
</MudText> </MudText>
</div> </div>
</div> </div>
<MudGrid Spacing="2"> <MudGrid Spacing="2">
<MudItem xs="12" md="4"> <MudItem xs="12" md="4">
<MudSwitch @bind-Value="_exportSettings.AuditCsvEnabled" Label="Audit-CSV je Standort schreiben" Color="Color.Primary" /> <MudSwitch @bind-Value="_exportSettings.AuditCsvEnabled" Label="@T("Audit-CSV je Standort schreiben", "Write audit CSV per site")" Color="Color.Primary" />
<MudText Typo="Typo.caption"> <MudText Typo="Typo.caption">
Schreibt beim Laenderexport je Standort eine Sales_*.csv mit den transformierten Daten. @T("Schreibt beim Laenderexport je Standort eine Sales_*.csv mit den transformierten Daten.",
"Writes one Sales_*.csv per site during country export with the transformed data.")
</MudText> </MudText>
</MudItem> </MudItem>
<MudItem xs="12" md="4"> <MudItem xs="12" md="4">
<MudSwitch @bind-Value="_exportSettings.UseAuditCsvAsCentralSource" Label="Zentrale Auswertung aus Audit-CSV" Color="Color.Warning" /> <MudSwitch @bind-Value="_exportSettings.UseAuditCsvAsCentralSource" Label="@T("Zentrale Auswertung aus Audit-CSV", "Central analysis from audit CSV")" Color="Color.Warning" />
<MudText Typo="Typo.caption"> <MudText Typo="Typo.caption">
Dashboard, zentrale Excel-Datei und Finance-Auswertungen lesen die neuesten Standort-CSV-Dateien statt CentralSalesRecords. @T("Dashboard, zentrale Excel-Datei und Finance-Auswertungen lesen die neuesten Standort-CSV-Dateien statt CentralSalesRecords.",
"Dashboard, central Excel file and finance analyses read the latest site CSV files instead of CentralSalesRecords.")
</MudText> </MudText>
</MudItem> </MudItem>
<MudItem xs="12" md="4"> <MudItem xs="12" md="4">
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Filled"> <MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Filled">
Audit-CSV wird immer im gleichen Ordner wie die lokalen Standort-Dateien abgelegt. @((MarkupString)T("Audit-CSV wird immer im gleichen Ordner wie die lokalen Standort-Dateien abgelegt. Der Pfad wird oben bei <b>Lokaler Standardpfad Standort-Dateien</b> gesetzt.",
Der Pfad wird oben bei <b>Lokaler Standardpfad Standort-Dateien</b> gesetzt. "Audit CSV is always stored in the same folder as the local site files. The path is set above under <b>Local default path for site files</b>."))
</MudAlert> </MudAlert>
</MudItem> </MudItem>
</MudGrid> </MudGrid>
@@ -328,21 +336,21 @@
<MudItem xs="12"> <MudItem xs="12">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveExportSettings" <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveExportSettings"
StartIcon="@Icons.Material.Filled.Save"> StartIcon="@Icons.Material.Filled.Save">
Speichern @T("Speichern", "Save")
</MudButton> </MudButton>
</MudItem> </MudItem>
</MudGrid> </MudGrid>
</MudPaper> </MudPaper>
@* Filename Preview *@ @* Filename Preview *@
<MudText Typo="Typo.h5" Class="mb-2">Dateiname Vorschau</MudText> <MudText Typo="Typo.h5" Class="mb-2">@T("Dateiname Vorschau", "Filename preview")</MudText>
<MudPaper Class="pa-4" Elevation="1"> <MudPaper Class="pa-4" Elevation="1">
<MudText Typo="Typo.body1"> <MudText Typo="Typo.body1">
<MudIcon Icon="@Icons.Material.Filled.InsertDriveFile" Size="Size.Small" Class="mr-1" /> <MudIcon Icon="@Icons.Material.Filled.InsertDriveFile" Size="Size.Small" Class="mr-1" />
Sales_{"{TSC}"}_{DateTime.Now:yyyy-MM-dd}.xlsx Sales_{"{TSC}"}_{DateTime.Now:yyyy-MM-dd}.xlsx
</MudText> </MudText>
<MudText Typo="Typo.caption" Class="mt-1"> <MudText Typo="Typo.caption" Class="mt-1">
Beispiel: Sales_TRFR_@(DateTime.Now.ToString("yyyy-MM-dd")).xlsx @T("Beispiel:", "Example:") Sales_TRFR_@(DateTime.Now.ToString("yyyy-MM-dd")).xlsx
</MudText> </MudText>
</MudPaper> </MudPaper>
@@ -391,7 +399,7 @@
private async Task SaveSharePoint() private async Task SaveSharePoint()
{ {
await SettingsPageActions.SaveSharePointAsync(_spConfig); await SettingsPageActions.SaveSharePointAsync(_spConfig);
Snackbar.Add("SharePoint Konfiguration gespeichert", Severity.Success); Snackbar.Add(T("SharePoint Konfiguration gespeichert", "SharePoint configuration saved"), Severity.Success);
} }
private async Task TestSharePoint() private async Task TestSharePoint()
@@ -400,11 +408,11 @@
try try
{ {
_sharePointTestPreview = await SettingsPageActions.BuildSharePointTestPreviewAsync(_spConfig); _sharePointTestPreview = await SettingsPageActions.BuildSharePointTestPreviewAsync(_spConfig);
Snackbar.Add("SharePoint Verbindung erfolgreich!", Severity.Success); Snackbar.Add(T("SharePoint Verbindung erfolgreich!", "SharePoint connection successful!"), Severity.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
Snackbar.Add($"Verbindung fehlgeschlagen: {ex.Message}", Severity.Error); Snackbar.Add($"{T("Verbindung fehlgeschlagen", "Connection failed")}: {ex.Message}", Severity.Error);
} }
finally finally
{ {
@@ -415,7 +423,7 @@
private async Task SaveExportSettings() private async Task SaveExportSettings()
{ {
await SettingsPageActions.SaveExportSettingsAsync(_exportSettings); await SettingsPageActions.SaveExportSettingsAsync(_exportSettings);
Snackbar.Add("Export Einstellungen gespeichert", Severity.Success); Snackbar.Add(T("Export Einstellungen gespeichert", "Export settings saved"), Severity.Success);
} }
private void AddSourceSystem() private void AddSourceSystem()
@@ -457,13 +465,13 @@
if (string.IsNullOrWhiteSpace(_editingSourceSystem.Code) || string.IsNullOrWhiteSpace(_editingSourceSystem.DisplayName)) if (string.IsNullOrWhiteSpace(_editingSourceSystem.Code) || string.IsNullOrWhiteSpace(_editingSourceSystem.DisplayName))
{ {
Snackbar.Add("Code und Name fuer das Quellsystem sind Pflicht.", Severity.Warning); Snackbar.Add(T("Code und Name fuer das Quellsystem sind Pflicht.", "Code and name are required for the source system."), Severity.Warning);
return; return;
} }
if (_sourceSystems.Any(x => x.Id != _editingSourceSystem.Id && x.Code == _editingSourceSystem.Code)) if (_sourceSystems.Any(x => x.Id != _editingSourceSystem.Id && x.Code == _editingSourceSystem.Code))
{ {
Snackbar.Add($"Quellsystem-Code doppelt vorhanden: {_editingSourceSystem.Code}", Severity.Warning); Snackbar.Add($"{T("Quellsystem-Code doppelt vorhanden", "Duplicate source-system code")}: {_editingSourceSystem.Code}", Severity.Warning);
return; return;
} }
@@ -505,7 +513,7 @@
try try
{ {
_sourceSystems = await SettingsPageActions.SaveSourceSystemsAsync(_sourceSystems); _sourceSystems = await SettingsPageActions.SaveSourceSystemsAsync(_sourceSystems);
Snackbar.Add("Quellsysteme gespeichert", Severity.Success); Snackbar.Add(T("Quellsysteme gespeichert", "Source systems saved"), Severity.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -533,7 +541,7 @@
private async Task SaveExchangeRates() private async Task SaveExchangeRates()
{ {
_exchangeRates = await SettingsPageActions.SaveExchangeRatesAsync(_exchangeRates); _exchangeRates = await SettingsPageActions.SaveExchangeRatesAsync(_exchangeRates);
Snackbar.Add("Wechselkurse gespeichert", Severity.Success); Snackbar.Add(T("Wechselkurse gespeichert", "Exchange rates saved"), Severity.Success);
} }
private async Task RefreshEcbRates() private async Task RefreshEcbRates()
@@ -546,11 +554,11 @@
{ {
var result = await SettingsPageActions.RefreshEcbRatesAsync(); var result = await SettingsPageActions.RefreshEcbRatesAsync();
_exchangeRates = result.ExchangeRates; _exchangeRates = result.ExchangeRates;
Snackbar.Add($"ECB-Kurse aktualisiert: {result.ImportedCount} Kurse vom {result.RateDate:yyyy-MM-dd}.", Severity.Success); Snackbar.Add($"{T("ECB-Kurse aktualisiert", "ECB rates refreshed")}: {result.ImportedCount} {T("Kurse vom", "rates from")} {result.RateDate:yyyy-MM-dd}.", Severity.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
Snackbar.Add($"ECB-Kursimport fehlgeschlagen: {ex.Message}", Severity.Error); Snackbar.Add($"{T("ECB-Kursimport fehlgeschlagen", "ECB rate import failed")}: {ex.Message}", Severity.Error);
} }
finally finally
{ {
@@ -570,11 +578,11 @@
var suffix = _includeSecretsInExport ? "with-secrets" : "without-secrets"; var suffix = _includeSecretsInExport ? "with-secrets" : "without-secrets";
var fileName = $"trafag-config-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{suffix}.json"; var fileName = $"trafag-config-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{suffix}.json";
await JS.InvokeVoidAsync("trafagDownload.saveTextFile", fileName, json, "application/json;charset=utf-8"); await JS.InvokeVoidAsync("trafagDownload.saveTextFile", fileName, json, "application/json;charset=utf-8");
Snackbar.Add("Konfiguration exportiert", Severity.Success); Snackbar.Add(T("Konfiguration exportiert", "Configuration exported"), Severity.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
Snackbar.Add($"Export fehlgeschlagen: {ex.Message}", Severity.Error); Snackbar.Add($"{T("Export fehlgeschlagen", "Export failed")}: {ex.Message}", Severity.Error);
} }
finally finally
{ {
@@ -599,11 +607,11 @@
_exportSettings = state.ExportSettings; _exportSettings = state.ExportSettings;
_sourceSystems = state.SourceSystems; _sourceSystems = state.SourceSystems;
_exchangeRates = state.ExchangeRates; _exchangeRates = state.ExchangeRates;
Snackbar.Add("Konfiguration importiert", Severity.Success); Snackbar.Add(T("Konfiguration importiert", "Configuration imported"), Severity.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
Snackbar.Add($"Import fehlgeschlagen: {ex.Message}", Severity.Error); Snackbar.Add($"{T("Import fehlgeschlagen", "Import failed")}: {ex.Message}", Severity.Error);
} }
finally finally
{ {
@@ -616,7 +624,7 @@
var definition = _sourceSystems.FirstOrDefault(x => string.Equals(x.Code, sourceSystem, StringComparison.OrdinalIgnoreCase)); var definition = _sourceSystems.FirstOrDefault(x => string.Equals(x.Code, sourceSystem, StringComparison.OrdinalIgnoreCase));
if (definition is null) if (definition is null)
{ {
Snackbar.Add($"Quellsystem '{sourceSystem}' nicht gefunden.", Severity.Warning); Snackbar.Add($"{T("Quellsystem nicht gefunden", "Source system not found")}: {sourceSystem}", Severity.Warning);
return; return;
} }
@@ -659,5 +667,7 @@
=> string.IsNullOrWhiteSpace(definition.CentralUsername) ? "-" : definition.CentralUsername; => string.IsNullOrWhiteSpace(definition.CentralUsername) ? "-" : definition.CentralUsername;
private static string NormalizeConfigValue(string? value) => Services.SettingsPageService.NormalizeConfigValue(value); private static string NormalizeConfigValue(string? value) => Services.SettingsPageService.NormalizeConfigValue(value);
private string T(string german, string english) => UiText.Text(german, english);
} }