Files
radzen-blazor/Radzen.Blazor/Spreadsheet/SpreadsheetDialogBase.cs
Atanas Korchev 609622692e Add culture-aware editing, display, and formula entry to RadzenSpreadsheet
The spreadsheet engine parsed typed input with the invariant culture while
displaying values with the current culture, breaking the edit round-trip on
non-en-US hosts and making comma-decimal entry (10,50) impossible.

Workbook gains a Culture property (defaults to CurrentCulture) which the
component stamps from its inherited Culture parameter. It drives:

- Cell input parsing (CellData type inference), including day-month date
  handling for cultures where the group and date separators collide (de-DE)
- Edit text and display rendering (GetValue, GetValueAsString, GetDisplayText)
- Number format rendering - format codes stay canonical invariant tokens while
  separators, month names, and AM/PM designators follow the culture
- Formula entry and display via the new FormulaLocalizer (Excel FormulaLocal
  semantics: ';' argument separators and ',' decimals in comma-decimal
  cultures, lenient comma acceptance where unambiguous, canonical invariant
  storage)
- Dialog input (data validation, conditional format, filter) via shared
  conversion helpers on SpreadsheetDialogBase

XLSX and CSV files read and write canonical invariant values regardless of
the workbook culture, including autofit column widths. Malformed formulas now
surface as error trees instead of an unhandled lexer exception. Number format
parsing is cached (hard-bounded) since CellView reparsed per render.

Includes localization demos for the Spreadsheet and Document Processing
sections and culture test suites.

Note: headless code on non-en-US hosts now parses string values with the
host culture; set Workbook.Culture explicitly (or to InvariantCulture) for
host-independent processing.
2026-07-08 14:54:55 +03:00

69 lines
2.6 KiB
C#

using System.Globalization;
using Microsoft.AspNetCore.Components;
namespace Radzen.Blazor.Spreadsheet;
#nullable enable
/// <summary>
/// Base class for spreadsheet dialogs that provides shared <see cref="DialogService"/> injection,
/// a localizer, and a default cancel handler.
/// </summary>
public abstract class SpreadsheetDialogBase : ComponentBase
{
/// <summary>
/// Gets or sets the dialog service used to close the dialog.
/// </summary>
[Inject]
protected DialogService DialogService { get; set; } = default!;
/// <summary>
/// Gets or sets the localizer used to resolve resource strings.
/// </summary>
[Inject]
internal Localizer Localizer { get; set; } = default!;
/// <summary>
/// The UI culture cascaded from a parent component. Dialogs render outside the component that
/// opened them, so this resolves only when a <c>DefaultUICulture</c> cascade is set above the
/// dialog host; otherwise <see cref="L"/> falls back to <see cref="CultureInfo.CurrentUICulture"/>.
/// </summary>
[CascadingParameter(Name = "DefaultUICulture")]
protected CultureInfo? DefaultUICulture { get; set; }
/// <summary>
/// Returns the localized string for the specified resource key in the current UI culture.
/// </summary>
protected string L(string key) => Localizer.Get(key, DefaultUICulture ?? CultureInfo.CurrentUICulture);
/// <summary>
/// The culture used to parse and format values entered in the dialog. The spreadsheet passes
/// its workbook culture when opening the dialog.
/// </summary>
[Parameter]
public CultureInfo Culture { get; set; } = CultureInfo.CurrentCulture;
/// <summary>
/// Converts a numeric string entered in <see cref="Culture"/> to the canonical invariant form
/// stored by the engine. Non-numeric text is returned verbatim.
/// </summary>
protected string ToInvariantNumber(string value) =>
double.TryParse(value, NumberStyles.Any, Culture, out var number)
? number.ToString(CultureInfo.InvariantCulture)
: value;
/// <summary>
/// Converts a canonical invariant numeric string to <see cref="Culture"/> for display.
/// Non-numeric text is returned verbatim.
/// </summary>
protected string ToLocalNumber(string value) =>
double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var number)
? number.ToString(Culture)
: value;
/// <summary>
/// Closes the dialog without returning a result.
/// </summary>
protected virtual void OnCancel() => DialogService.Close();
}