Skip to content

Commit 3bb46f3

Browse files
committed
Add PluralForm tracking and SourceText restoration to sync history
- Add PluralForm field to SyncChangeEntry for tracking plural form changes - Update SyncHistoryService.RevertToAsync to restore SourceText/SourcePluralText - Include PluralForm in change entries for KeySyncService and ResourceService - Backward compatible: old history entries default to empty PluralForm
1 parent a2e95fa commit 3bb46f3

4 files changed

Lines changed: 201 additions & 8 deletions

File tree

cloud/src/LrmCloud.Api/Services/KeySyncService.cs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,12 @@ public async Task<KeySyncPushResponse> PushAsync(
104104
}
105105
response.NewEntryHashes[entry.Key][entry.Lang] = result.NewHash!;
106106

107-
// Track change for history
107+
// Track change for history (PluralForm = "" for non-plural entries)
108108
historyChanges.Add(new Entities.SyncChangeEntry
109109
{
110110
Key = entry.Key,
111111
Lang = entry.Lang,
112+
PluralForm = "",
112113
ChangeType = result.WasNew ? "added" : "modified",
113114
BeforeValue = result.BeforeValue,
114115
BeforeHash = result.BeforeHash,
@@ -133,11 +134,12 @@ public async Task<KeySyncPushResponse> PushAsync(
133134
{
134135
response.Deleted++;
135136

136-
// Track deletion for history
137+
// Track deletion for history (PluralForm = "" for non-plural entries)
137138
historyChanges.Add(new Entities.SyncChangeEntry
138139
{
139140
Key = deletion.Key,
140141
Lang = deletion.Lang ?? result.DeletedLang ?? "all",
142+
PluralForm = "",
141143
ChangeType = "deleted",
142144
BeforeValue = result.DeletedValue,
143145
BeforeHash = result.DeletedHash,
@@ -256,6 +258,7 @@ public async Task<KeySyncPullResponse> PullAsync(
256258
Key = key.KeyName,
257259
Comment = key.Comment,
258260
IsPlural = key.IsPlural,
261+
SourceText = key.SourceText,
259262
SourcePluralText = key.SourcePluralText
260263
};
261264

@@ -404,6 +407,8 @@ private async Task<EntryChangeResult> ApplyEntryChangeAsync(
404407
KeyName = entry.Key,
405408
IsPlural = entry.IsPlural,
406409
Comment = entry.Comment,
410+
// Store source text (from default language file, msgid for PO format)
411+
SourceText = entry.SourceText,
407412
// For plural keys, store source plural text (PO msgid_plural or "other" form)
408413
SourcePluralText = entry.IsPlural ? entry.SourcePluralText : null,
409414
Version = 1,
@@ -425,8 +430,14 @@ private async Task<EntryChangeResult> ApplyEntryChangeAsync(
425430
resourceKey.IsPlural = entry.IsPlural;
426431
resourceKey.UpdatedAt = DateTime.UtcNow;
427432
}
428-
// Update SourcePluralText if not set yet
429-
if (entry.IsPlural && resourceKey.SourcePluralText == null && entry.SourcePluralText != null)
433+
// Update SourceText if provided (from source/default language push)
434+
if (!string.IsNullOrEmpty(entry.SourceText) && resourceKey.SourceText != entry.SourceText)
435+
{
436+
resourceKey.SourceText = entry.SourceText;
437+
resourceKey.UpdatedAt = DateTime.UtcNow;
438+
}
439+
// Update SourcePluralText if provided
440+
if (entry.IsPlural && !string.IsNullOrEmpty(entry.SourcePluralText) && resourceKey.SourcePluralText != entry.SourcePluralText)
430441
{
431442
resourceKey.SourcePluralText = entry.SourcePluralText;
432443
resourceKey.UpdatedAt = DateTime.UtcNow;
@@ -771,6 +782,7 @@ private async Task ApplyEntryResolutionAsync(
771782
{
772783
var resourceKey = await _db.ResourceKeys
773784
.Include(k => k.Translations)
785+
.Include(k => k.Project)
774786
.FirstOrDefaultAsync(k => k.ProjectId == projectId && k.KeyName == resolution.Key, ct);
775787

776788
if (resourceKey == null)
@@ -781,6 +793,9 @@ private async Task ApplyEntryResolutionAsync(
781793
var translation = resourceKey.Translations
782794
.FirstOrDefault(t => t.LanguageCode == resolution.Lang && t.PluralForm == "");
783795

796+
// Check if this is the default language for SourceText updates
797+
var isDefaultLanguage = resourceKey.Project?.DefaultLanguage == resolution.Lang;
798+
784799
switch (resolution.Resolution)
785800
{
786801
case "Local":
@@ -793,6 +808,13 @@ private async Task ApplyEntryResolutionAsync(
793808
translation.UpdatedAt = DateTime.UtcNow;
794809
response.Applied++;
795810

811+
// Update SourceText if this is the default language
812+
if (isDefaultLanguage)
813+
{
814+
resourceKey.SourceText = resolution.EditedValue;
815+
resourceKey.UpdatedAt = DateTime.UtcNow;
816+
}
817+
796818
AddToHashes(response.NewHashes, resolution.Key, resolution.Lang!, translation.Hash);
797819
}
798820
break;
@@ -833,6 +855,14 @@ private async Task ApplyEntryResolutionAsync(
833855
translation.Version++;
834856
translation.UpdatedAt = DateTime.UtcNow;
835857
}
858+
859+
// Update SourceText if this is the default language
860+
if (isDefaultLanguage)
861+
{
862+
resourceKey.SourceText = resolution.EditedValue;
863+
resourceKey.UpdatedAt = DateTime.UtcNow;
864+
}
865+
836866
response.Applied++;
837867
AddToHashes(response.NewHashes, resolution.Key, resolution.Lang!, translation.Hash!);
838868
}

cloud/src/LrmCloud.Api/Services/ResourceService.cs

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ private IQueryable<ResourceKey> ApplySingleLanguageFilter(IQueryable<ResourceKey
297297
KeyPath = request.KeyPath,
298298
IsPlural = request.IsPlural,
299299
Comment = request.Comment,
300+
// Store the default language value as SourceText for translation reference
301+
SourceText = request.DefaultLanguageValue,
300302
Version = 1,
301303
CreatedAt = DateTime.UtcNow,
302304
UpdatedAt = DateTime.UtcNow
@@ -467,6 +469,10 @@ private IQueryable<ResourceKey> ApplySingleLanguageFilter(IQueryable<ResourceKey
467469
var translation = resourceKey.Translations
468470
.FirstOrDefault(t => t.LanguageCode == languageCode && t.PluralForm == request.PluralForm);
469471

472+
// Get project to check if we're updating the default language
473+
var project = await _db.Projects.FindAsync(projectId);
474+
var isDefaultLanguage = project != null && languageCode == project.DefaultLanguage;
475+
470476
if (translation == null)
471477
{
472478
// Create new translation
@@ -483,6 +489,18 @@ private IQueryable<ResourceKey> ApplySingleLanguageFilter(IQueryable<ResourceKey
483489
};
484490

485491
_db.Translations.Add(translation);
492+
493+
// If creating translation for default language, also update SourceText
494+
if (isDefaultLanguage && string.IsNullOrEmpty(request.PluralForm))
495+
{
496+
resourceKey.SourceText = request.Value;
497+
resourceKey.UpdatedAt = DateTime.UtcNow;
498+
}
499+
else if (isDefaultLanguage && request.PluralForm == "other")
500+
{
501+
resourceKey.SourcePluralText = request.Value;
502+
resourceKey.UpdatedAt = DateTime.UtcNow;
503+
}
486504
}
487505
else
488506
{
@@ -498,6 +516,18 @@ private IQueryable<ResourceKey> ApplySingleLanguageFilter(IQueryable<ResourceKey
498516
translation.Comment = request.Comment;
499517
translation.UpdatedAt = DateTime.UtcNow;
500518
translation.Version++;
519+
520+
// If updating the default language, also update SourceText
521+
if (isDefaultLanguage && string.IsNullOrEmpty(request.PluralForm))
522+
{
523+
resourceKey.SourceText = request.Value;
524+
resourceKey.UpdatedAt = DateTime.UtcNow;
525+
}
526+
else if (isDefaultLanguage && request.PluralForm == "other")
527+
{
528+
resourceKey.SourcePluralText = request.Value;
529+
resourceKey.UpdatedAt = DateTime.UtcNow;
530+
}
501531
}
502532

503533
await _db.SaveChangesAsync();
@@ -529,6 +559,10 @@ private IQueryable<ResourceKey> ApplySingleLanguageFilter(IQueryable<ResourceKey
529559
return (false, 0, "You don't have permission to manage resources in this project");
530560
}
531561

562+
// Get project to check if we're updating the default language
563+
var project = await _db.Projects.FindAsync(projectId);
564+
var isDefaultLanguage = project != null && languageCode == project.DefaultLanguage;
565+
532566
var updatedCount = 0;
533567

534568
foreach (var update in updates)
@@ -578,6 +612,18 @@ private IQueryable<ResourceKey> ApplySingleLanguageFilter(IQueryable<ResourceKey
578612
translation.Version++;
579613
}
580614

615+
// If updating the default language, also update SourceText
616+
if (isDefaultLanguage && string.IsNullOrEmpty(update.PluralForm))
617+
{
618+
resourceKey.SourceText = update.Value;
619+
resourceKey.UpdatedAt = DateTime.UtcNow;
620+
}
621+
else if (isDefaultLanguage && update.PluralForm == "other")
622+
{
623+
resourceKey.SourcePluralText = update.Value;
624+
resourceKey.UpdatedAt = DateTime.UtcNow;
625+
}
626+
581627
updatedCount++;
582628
}
583629

@@ -1023,6 +1069,7 @@ private ResourceKeyDto MapToResourceKeyDto(ResourceKey key)
10231069
KeyName = key.KeyName,
10241070
KeyPath = key.KeyPath,
10251071
IsPlural = key.IsPlural,
1072+
SourceText = key.SourceText,
10261073
SourcePluralText = key.SourcePluralText,
10271074
Comment = key.Comment,
10281075
Version = key.Version,
@@ -1039,19 +1086,47 @@ private ResourceKeyDetailDto MapToResourceKeyDetailDto(ResourceKey key)
10391086

10401087
private ResourceKeyDetailDto MapToResourceKeyDetailDto(ResourceKey key, string? defaultLanguage)
10411088
{
1089+
var translations = key.Translations.Select(t => MapToTranslationDto(t, defaultLanguage)).ToList();
1090+
1091+
// Add virtual translation for source language display if SourceText exists
1092+
// but no translation exists for the default language (or empty LanguageCode)
1093+
if (!string.IsNullOrEmpty(defaultLanguage) && !string.IsNullOrEmpty(key.SourceText) && !key.IsPlural)
1094+
{
1095+
var hasDefaultLangTranslation = key.Translations.Any(t =>
1096+
(t.LanguageCode == defaultLanguage || string.IsNullOrEmpty(t.LanguageCode))
1097+
&& string.IsNullOrEmpty(t.PluralForm));
1098+
1099+
if (!hasDefaultLangTranslation)
1100+
{
1101+
// Insert virtual source translation for grid display
1102+
translations.Insert(0, new TranslationDto
1103+
{
1104+
Id = 0,
1105+
LanguageCode = defaultLanguage,
1106+
Value = key.SourceText,
1107+
PluralForm = "",
1108+
Status = "source",
1109+
Version = 0,
1110+
UpdatedAt = key.UpdatedAt,
1111+
IsVirtual = true
1112+
});
1113+
}
1114+
}
1115+
10421116
return new ResourceKeyDetailDto
10431117
{
10441118
Id = key.Id,
10451119
KeyName = key.KeyName,
10461120
KeyPath = key.KeyPath,
10471121
IsPlural = key.IsPlural,
1122+
SourceText = key.SourceText,
10481123
SourcePluralText = key.SourcePluralText,
10491124
Comment = key.Comment,
10501125
Version = key.Version,
10511126
TranslationCount = key.Translations.Count,
10521127
CreatedAt = key.CreatedAt,
10531128
UpdatedAt = key.UpdatedAt,
1054-
Translations = key.Translations.Select(t => MapToTranslationDto(t, defaultLanguage)).ToList()
1129+
Translations = translations
10551130
};
10561131
}
10571132

@@ -1371,6 +1446,9 @@ public async Task<BatchSaveResponse> BatchSaveWithHistoryAsync(
13711446
var response = new BatchSaveResponse();
13721447
var changes = new List<SyncChangeEntry>();
13731448

1449+
// Get project to check default language for SourceText updates
1450+
var project = await _db.Projects.FindAsync(projectId);
1451+
13741452
await using var transaction = await _db.Database.BeginTransactionAsync();
13751453

13761454
try
@@ -1402,6 +1480,7 @@ public async Task<BatchSaveResponse> BatchSaveWithHistoryAsync(
14021480
{
14031481
Key = keyChange.KeyName,
14041482
Lang = "",
1483+
PluralForm = "",
14051484
ChangeType = "modified",
14061485
BeforeComment = beforeComment,
14071486
AfterComment = keyChange.Comment
@@ -1483,13 +1562,29 @@ public async Task<BatchSaveResponse> BatchSaveWithHistoryAsync(
14831562
translation.Version++;
14841563
}
14851564

1565+
// If updating the default language, also update SourceText on ResourceKey
1566+
if (project != null && translationChange.LanguageCode == project.DefaultLanguage)
1567+
{
1568+
if (string.IsNullOrEmpty(pluralForm))
1569+
{
1570+
resourceKey.SourceText = translation.Value;
1571+
resourceKey.UpdatedAt = DateTime.UtcNow;
1572+
}
1573+
else if (pluralForm == "other")
1574+
{
1575+
resourceKey.SourcePluralText = translation.Value;
1576+
resourceKey.UpdatedAt = DateTime.UtcNow;
1577+
}
1578+
}
1579+
14861580
response.TranslationsModified++;
14871581

14881582
// Record the change
14891583
changes.Add(new SyncChangeEntry
14901584
{
14911585
Key = translationChange.KeyName,
14921586
Lang = translationChange.LanguageCode,
1587+
PluralForm = pluralForm,
14931588
ChangeType = changeType,
14941589
BeforeValue = beforeValue,
14951590
AfterValue = translation.Value,

0 commit comments

Comments
 (0)