Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- All header/footer types supported: Default, First Page, Even Page
- Same syntax and features as document body - no additional API calls needed
- Formatting is preserved in headers and footers
- **Number and Currency Format Specifiers** - Format numeric values directly in placeholders (#22)
- `:currency` — locale-aware currency formatting (e.g., `$1,234.56` or `1.234,56 €`)
- `:number:FORMAT` — any .NET numeric format string (e.g., `:number:N2`, `:number:F3`, `:number:P`)
- Works with int, long, decimal, double, and float values
- Supports compound format specifiers in placeholder regex
- **String Format Specifiers** - Transform string casing in placeholders (#22)
- `:uppercase` — convert to UPPERCASE
- `:lowercase` — convert to lowercase
- **Date Format Specifiers** - Format dates directly in placeholders (#22)
- `:date:FORMAT` — any .NET date format string (e.g., `:date:yyyy-MM-dd`, `:date:MMMM d, yyyy`)
- Supports DateTime, DateTimeOffset, and ISO date string values
- Culture-aware month/day names
- **GUI Culture Selector** - Dropdown to choose formatting culture (Invariant, en-US, de-DE, fr-FR, es-ES)

## [1.5.0] - 2026-02-13

Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,10 @@ The library uses a **visitor pattern** for processing Word documents, enabling:
- Nested: `{{Customer.Address.City}}`
- Array indexing: `{{Items[0].Name}}`
- Dictionary: `{{Settings[Theme]}}` or `{{Settings.Theme}}`
- Currency format: `{{Amount:currency}}`
- Number format: `{{Value:number:N2}}`, `{{Rate:number:F3}}`, `{{Pct:number:P}}`
- String format: `{{Name:uppercase}}`, `{{Code:lowercase}}`
- Date format: `{{OrderDate:date:yyyy-MM-dd}}`, `{{Date:date:MMMM d, yyyy}}`

### Conditional Syntax
```
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,32 @@ To disable (for backward compatibility):
var options = new PlaceholderReplacementOptions { EnableNewlineSupport = false };
```

### Format Specifiers

Control how values are displayed using format specifiers:

```
{{Name:uppercase}} → ALICE JOHNSON
{{Code:lowercase}} → abc-123
{{Amount:currency}} → $1,234.57 (en-US) or 1.234,57 € (de-DE)
{{Value:number:N2}} → 1,234.57
{{Percentage:number:P}} → 12.34 %
{{OrderDate:date:yyyy-MM-dd}} → 2024-01-15
{{OrderDate:date:MMMM d, yyyy}} → January 15, 2024
{{IsActive:checkbox}} → ☑ or ☐
{{IsActive:yesno}} → Yes or No
```

All format specifiers are culture-aware:

```csharp
var options = new PlaceholderReplacementOptions
{
Culture = new CultureInfo("de-DE") // Affects currency, numbers, dates, and localized text
};
var processor = new DocumentTemplateProcessor(options);
```

### Standalone Condition Evaluation

Use Templify's condition engine without processing Word documents:
Expand Down
14 changes: 14 additions & 0 deletions TriasDev.Templify.Gui/Models/CultureOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) 2025 TriasDev GmbH & Co. KG
// Licensed under the MIT License. See LICENSE file in the project root for full license information.

using System.Globalization;

namespace TriasDev.Templify.Gui.Models;

/// <summary>
/// Represents a culture option for the UI dropdown.
/// </summary>
public record CultureOption(string DisplayName, CultureInfo Culture)
{
public override string ToString() => DisplayName;
}
7 changes: 6 additions & 1 deletion TriasDev.Templify.Gui/Services/ITemplifyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License. See LICENSE file in the project root for full license information.

using System;
using System.Globalization;
using System.Threading.Tasks;
using TriasDev.Templify.Core;
using TriasDev.Templify.Gui.Models;
Expand All @@ -19,11 +20,13 @@ public interface ITemplifyService
/// <param name="templatePath">Path to the template file (.docx).</param>
/// <param name="jsonPath">Optional path to JSON data file for validation.</param>
/// <param name="enableHtmlEntityReplacement">Enable HTML entity replacement (e.g., &lt;br&gt; to line break).</param>
/// <param name="culture">Culture for formatting. Defaults to InvariantCulture.</param>
/// <returns>Validation result with errors and warnings.</returns>
Task<ValidationResult> ValidateTemplateAsync(
string templatePath,
string? jsonPath = null,
bool enableHtmlEntityReplacement = false);
bool enableHtmlEntityReplacement = false,
CultureInfo? culture = null);

/// <summary>
/// Processes a template with JSON data and generates output.
Expand All @@ -32,12 +35,14 @@ Task<ValidationResult> ValidateTemplateAsync(
/// <param name="jsonPath">Path to JSON data file.</param>
/// <param name="outputPath">Path for the output file.</param>
/// <param name="enableHtmlEntityReplacement">Enable HTML entity replacement (e.g., &lt;br&gt; to line break).</param>
/// <param name="culture">Culture for formatting. Defaults to InvariantCulture.</param>
/// <param name="progress">Optional progress reporter.</param>
/// <returns>Processing result with statistics and any errors.</returns>
Task<UiProcessingResult> ProcessTemplateAsync(
string templatePath,
string jsonPath,
string outputPath,
bool enableHtmlEntityReplacement = false,
CultureInfo? culture = null,
IProgress<double>? progress = null);
}
10 changes: 7 additions & 3 deletions TriasDev.Templify.Gui/Services/TemplifyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@ public class TemplifyService : ITemplifyService
public async Task<ValidationResult> ValidateTemplateAsync(
string templatePath,
string? jsonPath = null,
bool enableHtmlEntityReplacement = false)
bool enableHtmlEntityReplacement = false,
CultureInfo? culture = null)
{
return await Task.Run(() =>
{
CultureInfo effectiveCulture = culture ?? CultureInfo.InvariantCulture;
PlaceholderReplacementOptions options = new PlaceholderReplacementOptions
{
MissingVariableBehavior = MissingVariableBehavior.LeaveUnchanged,
Culture = CultureInfo.InvariantCulture,
Culture = effectiveCulture,
TextReplacements = enableHtmlEntityReplacement ? TextReplacements.HtmlEntities : null
};

Expand Down Expand Up @@ -63,6 +65,7 @@ public async Task<UiProcessingResult> ProcessTemplateAsync(
string jsonPath,
string outputPath,
bool enableHtmlEntityReplacement = false,
CultureInfo? culture = null,
IProgress<double>? progress = null)
{
return await Task.Run(() =>
Expand All @@ -83,10 +86,11 @@ public async Task<UiProcessingResult> ProcessTemplateAsync(
progress?.Report(0.3);

// Configure options with optional HTML entity replacement
CultureInfo effectiveCulture = culture ?? CultureInfo.InvariantCulture;
PlaceholderReplacementOptions options = new PlaceholderReplacementOptions
{
MissingVariableBehavior = MissingVariableBehavior.LeaveUnchanged,
Culture = CultureInfo.InvariantCulture,
Culture = effectiveCulture,
TextReplacements = enableHtmlEntityReplacement ? TextReplacements.HtmlEntities : null
};

Expand Down
25 changes: 24 additions & 1 deletion TriasDev.Templify.Gui/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
Expand Down Expand Up @@ -56,6 +57,24 @@ public partial class MainWindowViewModel : ViewModelBase
[ObservableProperty]
private bool _enableHtmlEntityReplacement;

/// <summary>
/// Available cultures for formatting.
/// </summary>
public ObservableCollection<CultureOption> AvailableCultures { get; } = new(
[
new CultureOption("Invariant", CultureInfo.InvariantCulture),
new CultureOption("English (US)", new CultureInfo("en-US")),
new CultureOption("German (DE)", new CultureInfo("de-DE")),
new CultureOption("French (FR)", new CultureInfo("fr-FR")),
new CultureOption("Spanish (ES)", new CultureInfo("es-ES")),
]);

/// <summary>
/// Gets or sets the selected culture for formatting.
/// </summary>
[ObservableProperty]
private CultureOption _selectedCulture = null!;

/// <summary>
/// Stores the last processing result to enable warning report generation.
/// </summary>
Expand All @@ -69,6 +88,7 @@ public MainWindowViewModel(
{
_templifyService = templifyService;
_fileDialogService = fileDialogService;
_selectedCulture = AvailableCultures[0];
}

[RelayCommand]
Expand Down Expand Up @@ -121,7 +141,8 @@ private async Task ValidateTemplateAsync()
ValidationResult validation = await _templifyService.ValidateTemplateAsync(
TemplatePath,
JsonPath,
EnableHtmlEntityReplacement);
EnableHtmlEntityReplacement,
SelectedCulture.Culture);

if (validation.IsValid)
{
Expand Down Expand Up @@ -196,6 +217,7 @@ private async Task ProcessTemplateAsync()
JsonPath,
OutputPath,
EnableHtmlEntityReplacement,
SelectedCulture.Culture,
progressReporter);

// Store for warning report generation
Expand Down Expand Up @@ -282,6 +304,7 @@ private void Clear()
StatusMessage = "Ready";
Progress = 0;
LastProcessingResult = null;
SelectedCulture = AvailableCultures[0];
}

[RelayCommand(CanExecute = nameof(CanGenerateWarningReport))]
Expand Down
9 changes: 8 additions & 1 deletion TriasDev.Templify.Gui/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,17 @@
</Grid>

<!-- Options Section -->
<StackPanel Grid.Row="4" Orientation="Horizontal" Margin="0,0,0,20">
<StackPanel Grid.Row="4" Orientation="Horizontal" Spacing="20" Margin="0,0,0,20">
<CheckBox Content="Enable HTML Entity Replacement"
IsChecked="{Binding EnableHtmlEntityReplacement}"
ToolTip.Tip="Convert HTML entities like &lt;br&gt;, &amp;nbsp;, &amp;lt;, etc. to Word equivalents"/>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="Culture:" VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding AvailableCultures}"
SelectedItem="{Binding SelectedCulture}"
Width="160"
ToolTip.Tip="Culture used for number, currency, date formatting and string casing"/>
</StackPanel>
</StackPanel>

<!-- Action Buttons -->
Expand Down
Loading
Loading