Skip to content

Commit d8c4560

Browse files
committed
Migrate web GUI to Radzen and propagate PO SourceText to cloud
Web UI: - Replace terminal-theme Blazor pages with Radzen.Blazor components across all pages (Editor, Index, Settings, Translation, etc.) - Add ReferencesDialog and TranslateKeyDialog - Add Radzen package reference and a build target that copies Radzen static assets into wwwroot/lib/radzen for single-file embedding - Wire up Radzen theme/scripts in _Layout.cshtml and _Imports.razor PO / cloud SourceText: - Add ResourceEntry.SourceText; PoResourceReader now stores msgid as SourceText in all PO files and uses msgid as the value for source (POT/default) files - Propagate SourceText/SourcePluralText through LocalEntry, LocalEntryExtractor, KeyLevelMerger and the EntryChange sync DTO Ops: - Raise nginx client_max_body_size to 100m for large push operations
1 parent c9fd8e6 commit d8c4560

66 files changed

Lines changed: 657714 additions & 2587 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Commands/WebCommand.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
using System.ComponentModel;
2323
using Microsoft.Extensions.FileProviders;
24+
using Radzen;
2425
using Spectre.Console;
2526
using Spectre.Console.Cli;
2627
using LocalizationManager.Core.Backends.Json;
@@ -147,6 +148,9 @@ public override int Execute(CommandContext context, Settings settings, Cancellat
147148
builder.Services.AddRazorPages();
148149
builder.Services.AddServerSideBlazor();
149150

151+
// Radzen Blazor components
152+
builder.Services.AddRadzenComponents();
153+
150154
// HttpClient for API communication (Blazor components will call localhost API)
151155
builder.Services.AddHttpClient("LrmApi", client =>
152156
{

LocalizationManager.Core/Backends/Po/PoResourceReader.cs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ private async Task<ResourceFile> ReadAsyncCore(TextReader reader, LanguageInfo m
8585
}
8686

8787
// Convert to ResourceEntry
88-
var resourceEntry = ConvertToResourceEntry(poEntry, metadata.Code);
88+
var resourceEntry = ConvertToResourceEntry(poEntry, metadata);
8989
entries.Add(resourceEntry);
9090
}
9191

@@ -302,30 +302,38 @@ private void AppendToCurrent(PoEntry entry, PoField field, string value)
302302
}
303303
}
304304

305-
private ResourceEntry ConvertToResourceEntry(PoEntry poEntry, string languageCode)
305+
private ResourceEntry ConvertToResourceEntry(PoEntry poEntry, LanguageInfo language)
306306
{
307307
var key = poEntry.GenerateKey(_config.KeyStrategy);
308308

309309
if (poEntry.IsPlural && poEntry.MsgStrPlural != null)
310310
{
311311
// Convert plural indices to CLDR categories
312312
var pluralForms = new Dictionary<string, string>();
313-
foreach (var (index, value) in poEntry.MsgStrPlural)
313+
foreach (var (index, pluralValue) in poEntry.MsgStrPlural)
314314
{
315-
var category = PoPluralMapper.IndexToCategory(languageCode, index);
316-
pluralForms[category] = value;
315+
var category = PoPluralMapper.IndexToCategory(language.Code, index);
316+
pluralForms[category] = pluralValue;
317317
}
318318

319+
// For source language (POT files), use msgid as value since msgstr is empty
320+
var entryValue = language.IsDefault
321+
? poEntry.MsgId ?? ""
322+
: (pluralForms.GetValueOrDefault("other")
323+
?? pluralForms.GetValueOrDefault("one")
324+
?? pluralForms.Values.FirstOrDefault()
325+
?? "");
326+
319327
return new ResourceEntry
320328
{
321329
Key = key,
322-
Value = pluralForms.GetValueOrDefault("other")
323-
?? pluralForms.GetValueOrDefault("one")
324-
?? pluralForms.Values.FirstOrDefault()
325-
?? "",
330+
Value = entryValue,
326331
Comment = poEntry.GetCombinedComment(),
327332
IsPlural = true,
328-
PluralForms = pluralForms,
333+
PluralForms = language.IsDefault ? null : pluralForms, // Don't store plural forms for source language
334+
// Store msgid as SourceText (the untranslated source) for all files, not just POT
335+
// This allows cloud to have the source text regardless of which language file it came from
336+
SourceText = poEntry.MsgId,
329337
// Store msgid_plural for translation (source plural text)
330338
SourcePluralText = poEntry.MsgIdPlural,
331339
References = poEntry.References, // Transfer references
@@ -338,11 +346,18 @@ private ResourceEntry ConvertToResourceEntry(PoEntry poEntry, string languageCod
338346
};
339347
}
340348

349+
// For source language (POT files), use msgid as value since msgstr is empty
350+
// In PO format: msgid = source text, msgstr = translation
351+
var value = language.IsDefault ? poEntry.MsgId : poEntry.MsgStr;
352+
341353
return new ResourceEntry
342354
{
343355
Key = key,
344-
Value = poEntry.MsgStr,
356+
Value = value,
345357
Comment = poEntry.GetCombinedComment(),
358+
// Store msgid as SourceText for all PO files (not just POT)
359+
// This ensures the cloud always has access to the source text
360+
SourceText = poEntry.MsgId,
346361
References = poEntry.References, // Transfer references
347362
// Preserve original formatting
348363
OriginalFormatting = poEntry.OriginalMsgStrLines != null && poEntry.OriginalMsgStrLines.Any()

LocalizationManager.Core/Cloud/KeyLevelMerger.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public PushChanges ComputePushChanges(
4848
Comment = entry.Comment,
4949
IsPlural = entry.IsPlural,
5050
PluralForms = entry.PluralForms,
51+
SourceText = entry.SourceText,
52+
SourcePluralText = entry.SourcePluralText,
5153
BaseHash = null
5254
});
5355
}
@@ -63,6 +65,8 @@ public PushChanges ComputePushChanges(
6365
Comment = entry.Comment,
6466
IsPlural = entry.IsPlural,
6567
PluralForms = entry.PluralForms,
68+
SourceText = entry.SourceText,
69+
SourcePluralText = entry.SourcePluralText,
6670
BaseHash = baseHash
6771
});
6872
}
@@ -497,6 +501,18 @@ public class LocalEntry
497501
public bool IsPlural { get; init; }
498502
public Dictionary<string, string>? PluralForms { get; init; }
499503
public required string Hash { get; init; }
504+
505+
/// <summary>
506+
/// Source text for the key (value from default language file, msgid for PO format).
507+
/// Only set for entries from the default/source language.
508+
/// </summary>
509+
public string? SourceText { get; init; }
510+
511+
/// <summary>
512+
/// Source plural text for plural keys (msgid_plural for PO, "other" form for others).
513+
/// Only set for plural entries from the default/source language.
514+
/// </summary>
515+
public string? SourcePluralText { get; init; }
500516
}
501517

502518
/// <summary>

LocalizationManager.Core/Cloud/LocalEntryExtractor.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,13 @@ public async Task<List<LocalEntry>> ExtractEntriesAsync(
5757
Comment = entry.Comment,
5858
IsPlural = entry.IsPlural,
5959
PluralForms = entry.PluralForms,
60-
Hash = hash
60+
Hash = hash,
61+
// Pass SourceText for storage in ResourceKey.SourceText
62+
// For default language: use entry.SourceText (e.g. msgid for PO) or fall back to Value
63+
// For non-default language: pass entry.SourceText if available (PO format has msgid in all files)
64+
SourceText = lang.IsDefault ? (entry.SourceText ?? entry.Value) : entry.SourceText,
65+
// Pass SourcePluralText (msgid_plural for PO, available in all PO files)
66+
SourcePluralText = entry.SourcePluralText
6167
});
6268
}
6369
}

LocalizationManager.Core/Cloud/Models/KeySyncDtos.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ public class EntryChange
9494
/// </summary>
9595
public Dictionary<string, string>? PluralForms { get; set; }
9696

97+
/// <summary>
98+
/// Source text for the key (value from default language, msgid for PO format).
99+
/// Only set when pushing from source/default language.
100+
/// </summary>
101+
public string? SourceText { get; set; }
102+
103+
/// <summary>
104+
/// For plural keys, the source plural text pattern (PO msgid_plural or "other" form).
105+
/// Only set when pushing from source/default language.
106+
/// </summary>
107+
public string? SourcePluralText { get; set; }
108+
97109
/// <summary>
98110
/// Hash of the entry from last sync (for conflict detection).
99111
/// Null if this is a new entry.

LocalizationManager.Core/Models/ResourceEntry.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ public class ResourceEntry
6161
/// </summary>
6262
public string? SourcePluralText { get; set; }
6363

64+
/// <summary>
65+
/// Source text for this entry (PO format: msgid).
66+
/// For PO format, this contains the msgid which is the untranslated source text.
67+
/// For other formats, this is typically populated with the Value from the default language file.
68+
/// </summary>
69+
public string? SourceText { get; set; }
70+
6471
/// <summary>
6572
/// Indicates if this entry is empty/null.
6673
/// For plural entries, checks if all plural forms are empty.

LocalizationManager.Tests/LocalizationManager.Tests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
<ItemGroup>
1111
<PackageReference Include="coverlet.collector" Version="6.0.2" />
1212
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
13-
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
14-
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
13+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
14+
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.2" />
1515
<PackageReference Include="xunit" Version="2.9.2" />
1616
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
1717
</ItemGroup>

LocalizationManager.csproj

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
<PackageReference Include="Spectre.Console.Cli" Version="0.53.0" />
3434
<PackageReference Include="Terminal.Gui" Version="1.19.0" />
3535
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
36+
37+
<!-- Web GUI components -->
38+
<PackageReference Include="Radzen.Blazor" Version="6.0.0" />
3639
</ItemGroup>
3740

3841
<ItemGroup>
@@ -92,4 +95,19 @@
9295
<EmbeddedResource Include="wwwroot\**\*" />
9396
</ItemGroup>
9497

98+
<!-- Copy Radzen static assets to wwwroot for embedding in single-file executable -->
99+
<Target Name="CopyRadzenAssets" BeforeTargets="Build" Condition="!Exists('wwwroot/lib/radzen/Radzen.Blazor.js')">
100+
<PropertyGroup>
101+
<RadzenPackagePath>$(NuGetPackageRoot)radzen.blazor/6.0.0/staticwebassets</RadzenPackagePath>
102+
</PropertyGroup>
103+
<ItemGroup>
104+
<RadzenCssFiles Include="$(RadzenPackagePath)/**/*.css" />
105+
<RadzenJsFile Include="$(RadzenPackagePath)/Radzen.Blazor.js" />
106+
</ItemGroup>
107+
<MakeDir Directories="wwwroot/lib/radzen" />
108+
<Copy SourceFiles="@(RadzenCssFiles)" DestinationFolder="wwwroot/lib/radzen/%(RecursiveDir)" SkipUnchangedFiles="true" />
109+
<Copy SourceFiles="@(RadzenJsFile)" DestinationFolder="wwwroot/lib/radzen" SkipUnchangedFiles="true" />
110+
<Message Importance="high" Text="Copied Radzen assets to wwwroot/lib/radzen/" />
111+
</Target>
112+
95113
</Project>

0 commit comments

Comments
 (0)