Skip to content

Commit a9e778c

Browse files
Ticket #887 : Error message coming from the BE can be translated
1 parent 13ed785 commit a9e778c

76 files changed

Lines changed: 1120 additions & 189 deletions

File tree

Some content is hidden

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

formbuilder/FormBuilder.EF/Configurations/FormRecordConfiguration.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ public class FormRecordConfiguration : IEntityTypeConfiguration<FormRecord>
1313
public void Configure(EntityTypeBuilder<FormRecord> builder)
1414
{
1515
builder.HasKey(f => f.Id);
16+
builder.Property(w => w.SuccessMessageTranslations).HasConversion(
17+
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
18+
v => string.IsNullOrWhiteSpace(v) ? null : JsonSerializer.Deserialize<List<FormMessageTranslation>>(v, (JsonSerializerOptions)null));
19+
builder.Property(w => w.ErrorMessageTranslations).HasConversion(
20+
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
21+
v => string.IsNullOrWhiteSpace(v) ? null : JsonSerializer.Deserialize<List<FormMessageTranslation>>(v, (JsonSerializerOptions)null));
1622
builder.Property(w => w.Elements).HasConversion(
1723
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
1824
v => string.IsNullOrWhiteSpace(v) ? null : new ObservableCollection<IFormElementRecord>(JsonSerializer.Deserialize<List<IFormElementRecord>>(v, (JsonSerializerOptions)null)));

formbuilder/FormBuilder/Components/Form/FormEditor.razor

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
<RadzenButton Icon="play_arrow" Text="Debug" Click="@(async () => await LaunchDebug())" ButtonStyle="ButtonStyle.Secondary" />
5555
<RadzenButton Icon="draft" Text="Json" Click="@(() => SwitchPanel(FormPanelTypes.JSON))" ButtonStyle="ButtonStyle.Secondary" />
5656
<RadzenButton Icon="css" Text="Css" Click="@(() => SwitchPanel(FormPanelTypes.CSS))" ButtonStyle="ButtonStyle.Secondary" />
57+
<RadzenButton Icon="data_object" Text="Translations" Click="@(() => SwitchPanel(FormPanelTypes.TRANSLATION))" ButtonStyle="ButtonStyle.Secondary"></RadzenButton>
5758
@if (CanSave)
5859
{
5960
<RadzenButton Icon="save" Text="Save" Style="margin-left: 5px" Click="@(async() => await Save())" Disabled="@isSaveDisabled" />
@@ -136,6 +137,11 @@
136137
{
137138
<JsonPanelComponent @ref=jsonPanelComponent Json="@SerializedForm" JsonChanged="HandleJsonChanged"></JsonPanelComponent>
138139
}
140+
141+
@if(panelType == FormPanelTypes.TRANSLATION)
142+
{
143+
<TranslationPanelComponent AllSupportedLanguages=@SupportedLanguageCodes @bind-SuccessMessageTranslations="@Form.SuccessMessageTranslations" @bind-ErrorMessageTranslations="@Form.ErrorMessageTranslations"></TranslationPanelComponent>
144+
}
139145
</div>
140146
</RadzenSplitterPane>
141147
</RadzenSplitter>

formbuilder/FormBuilder/Components/Form/FormPanelTypes.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
public enum FormPanelTypes
44
{
55
CSS = 0,
6-
JSON = 1
6+
JSON = 1,
7+
TRANSLATION = 2
78
}

formbuilder/FormBuilder/Components/Form/FormViewer.razor

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
@foreach (var errorMessage in Context.Execution.ErrorMessages)
5757
{
5858
<RadzenAlert AlertStyle="AlertStyle.Danger" Variant="Variant.Flat" Shade="Shade.Lighter">
59-
@errorMessage
59+
@TranslateErrorMessage(errorMessage)
6060
</RadzenAlert>
6161
}
6262
}
@@ -66,7 +66,7 @@
6666
@foreach (var successMessage in Context.Execution.SuccessMessages)
6767
{
6868
<RadzenAlert AlertStyle="AlertStyle.Success" Variant="Variant.Flat" Shade="Shade.Lighter">
69-
@successMessage
69+
@TranslateSuccessMessage(successMessage)
7070
</RadzenAlert>
7171
}
7272
}
@@ -200,6 +200,30 @@
200200
Init();
201201
}
202202

203+
private string TranslateSuccessMessage(string message)
204+
{
205+
var currentCulture = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
206+
var translation = Form.SuccessMessageTranslations.SingleOrDefault(t => t.Code == message && t.Language == currentCulture);
207+
if(translation == null)
208+
{
209+
return message;
210+
}
211+
212+
return translation.Value;
213+
}
214+
215+
private string TranslateErrorMessage(string message)
216+
{
217+
var currentCulture = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
218+
var translation = Form.ErrorMessageTranslations.SingleOrDefault(t => t.Code == message && t.Language == currentCulture);
219+
if (translation == null)
220+
{
221+
return message;
222+
}
223+
224+
return translation.Value;
225+
}
226+
203227
private void Init()
204228
{
205229
Form = Context.GetCurrentFormRecord();
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
@using FormBuilder.Models
2+
3+
<RadzenTabs @bind-SelectedIndex="@selectedIndex">
4+
<Tabs>
5+
<RadzenTabsItem Text="Success messages">
6+
<RadzenText TextStyle="TextStyle.Subtitle1">Success messages</RadzenText>
7+
<hr />
8+
<RadzenDataGrid Data="@GetGroupedTranslations(SuccessMessageTranslations)">
9+
<Columns>
10+
<RadzenDataGridColumn Property="Key" Title="Code"></RadzenDataGridColumn>
11+
<RadzenDataGridColumn Title="Translations">
12+
<Template Context="data">
13+
<RadzenStack>
14+
@foreach (var language in AllSupportedLanguages)
15+
{
16+
<RadzenRow>
17+
<RadzenColumn Size="6">
18+
<RadzenTextBox Disabled=true Value="@language"></RadzenTextBox>
19+
</RadzenColumn>
20+
<RadzenColumn Size="6">
21+
<RadzenTextBox Value="@GetTranslation(language, data.Key, SuccessMessageTranslations)" ValueChanged="@((newValue) => UpdateTranslation(language, data.Key, SuccessMessageTranslations, newValue, SuccessMessageTranslationsChanged))"></RadzenTextBox>
22+
</RadzenColumn>
23+
</RadzenRow>
24+
}
25+
</RadzenStack>
26+
</Template>
27+
</RadzenDataGridColumn>
28+
</Columns>
29+
</RadzenDataGrid>
30+
</RadzenTabsItem>
31+
<RadzenTabsItem Text="Error messages">
32+
<RadzenText TextStyle="TextStyle.Subtitle1">Error messages</RadzenText>
33+
<hr />
34+
<RadzenDataGrid Data="@GetGroupedTranslations(ErrorMessageTranslations)">
35+
<Columns>
36+
<RadzenDataGridColumn Property="Key" Title="Code"></RadzenDataGridColumn>
37+
<RadzenDataGridColumn Title="Translations">
38+
<Template Context="data">
39+
<RadzenStack>
40+
@foreach (var language in AllSupportedLanguages)
41+
{
42+
<RadzenRow>
43+
<RadzenColumn Size="2">
44+
<RadzenLabel>@language</RadzenLabel>
45+
</RadzenColumn>
46+
<RadzenColumn Size="10">
47+
<RadzenTextBox class="fullWidth" Value="@GetTranslation(language, data.Key, ErrorMessageTranslations)" ValueChanged="@((newValue) => UpdateTranslation(language, data.Key, ErrorMessageTranslations, newValue, ErrorMessageTranslationsChanged))"></RadzenTextBox>
48+
</RadzenColumn>
49+
</RadzenRow>
50+
}
51+
</RadzenStack>
52+
</Template>
53+
</RadzenDataGridColumn>
54+
</Columns>
55+
</RadzenDataGrid>
56+
</RadzenTabsItem>
57+
</Tabs>
58+
</RadzenTabs>
59+
60+
@code {
61+
int selectedIndex { get; set; } = 0;
62+
63+
[Parameter]
64+
public List<FormMessageTranslation> SuccessMessageTranslations
65+
{
66+
get; set;
67+
} = new List<FormMessageTranslation>();
68+
69+
[Parameter]
70+
public EventCallback<List<FormMessageTranslation>> SuccessMessageTranslationsChanged { get; set; }
71+
72+
[Parameter]
73+
public List<FormMessageTranslation> ErrorMessageTranslations
74+
{
75+
get; set;
76+
} = new List<FormMessageTranslation>();
77+
78+
[Parameter]
79+
public EventCallback<List<FormMessageTranslation>> ErrorMessageTranslationsChanged { get; set; }
80+
81+
[Parameter]
82+
public List<string> AllSupportedLanguages
83+
{
84+
get; set;
85+
} = new List<string>();
86+
87+
private Dictionary<string, List<FormMessageTranslation>> GetGroupedTranslations(List<FormMessageTranslation> messageTranslations)
88+
{
89+
return messageTranslations
90+
.GroupBy(mt => mt.Code)
91+
.ToDictionary(g => g.Key, g => g.ToList());
92+
}
93+
94+
private async Task UpdateTranslation(string language, string code, List<FormMessageTranslation> translations, string newValue, EventCallback<List<FormMessageTranslation>> changed)
95+
{
96+
var selectedTranslation = translations.SingleOrDefault(t => t.Language == language && t.Code == code);
97+
if(selectedTranslation != null)
98+
{
99+
selectedTranslation.Value = newValue;
100+
}
101+
else
102+
{
103+
translations.Add(new FormMessageTranslation
104+
{
105+
Code = code,
106+
Language = language,
107+
Value = newValue
108+
});
109+
}
110+
111+
await changed.InvokeAsync(translations);
112+
}
113+
114+
private string GetTranslation(string language, string code, List<FormMessageTranslation> translations)
115+
{
116+
var selectedTranslation = translations.SingleOrDefault(t => t.Language == language && t.Code == code);
117+
return selectedTranslation?.Value;
118+
}
119+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace FormBuilder.Models;
2+
3+
public class FormMessageTranslation
4+
{
5+
public string Language
6+
{
7+
get; set;
8+
}
9+
10+
public string Code
11+
{
12+
get; set;
13+
}
14+
15+
public string Value
16+
{
17+
get; set;
18+
}
19+
}

formbuilder/FormBuilder/Models/FormRecord.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ public class FormRecord : BaseVersionRecord, ICloneable, IFormRecordCollection
1414
public string? Category { get; set; }
1515
public string? Realm { get; set; }
1616
public bool ActAsStep { get; set; }
17+
public List<FormMessageTranslation> SuccessMessageTranslations
18+
{
19+
get; set;
20+
} = new List<FormMessageTranslation>();
21+
public List<FormMessageTranslation> ErrorMessageTranslations
22+
{
23+
get; set;
24+
} = new List<FormMessageTranslation>();
1725
public ObservableCollection<IFormElementRecord> Elements { get; set; } = new ObservableCollection<IFormElementRecord>();
1826

1927
public object Clone()
@@ -32,13 +40,31 @@ public object Clone()
3240
Status = Status,
3341
UpdateDateTime = UpdateDateTime,
3442
VersionNumber = VersionNumber,
35-
Elements = elements
43+
Elements = elements,
44+
SuccessMessageTranslations = SuccessMessageTranslations.Select(s => new FormMessageTranslation
45+
{
46+
Code = s.Code,
47+
Language = s.Language,
48+
Value = s.Value
49+
}).ToList(),
50+
ErrorMessageTranslations = ErrorMessageTranslations.Select(s => new FormMessageTranslation
51+
{
52+
Code = s.Code,
53+
Language = s.Language,
54+
Value = s.Value
55+
}).ToList()
3656
};
3757
}
3858

39-
public void Update(List<IFormElementRecord> elements, DateTime dateTime)
59+
public void Update(
60+
List<IFormElementRecord> elements,
61+
List<FormMessageTranslation> successMessageTranslations,
62+
List<FormMessageTranslation> errorMessageTranslations,
63+
DateTime dateTime)
4064
{
4165
UpdateDateTime = dateTime;
66+
SuccessMessageTranslations = successMessageTranslations;
67+
ErrorMessageTranslations = errorMessageTranslations;
4268
Elements = new ObservableCollection<IFormElementRecord>(elements);
4369
}
4470

formbuilder/FormBuilder/UIs/WorkflowViewModel.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,30 @@ public class WorkflowViewModel
1717
public List<string> SuccessMessages { get; set; }
1818

1919
public void SetErrorMessage(string errorMessage)
20-
=> ErrorMessages = new List<string> { errorMessage };
20+
{
21+
ErrorMessages = new List<string>
22+
{
23+
errorMessage
24+
};
25+
}
26+
27+
public void SetErrorMessages(List<string> errorMessages)
28+
{
29+
ErrorMessages = errorMessages;
30+
}
2131

2232
public void SetSuccessMessage(string successMessage)
23-
=> SuccessMessages = new List<string> { successMessage };
33+
{
34+
SuccessMessages = new List<string>
35+
{
36+
successMessage
37+
};
38+
}
39+
40+
public void SetSuccessMessages(List<string> successMessages)
41+
{
42+
SuccessMessages = successMessages;
43+
}
2444

2545
public void SetInput<T>(T record) where T : class
2646
{

src/IdServer/SimpleIdServer.IdServer.Email/IdServerBuilderExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public static IdServerBuilder AddEmailAuthentication(this IdServerBuilder idServ
3030
idServerBuilder.Services.AddTransient<IWorkflowLayoutService, EmailAuthWorkflowLayout>();
3131
idServerBuilder.Services.AddTransient<IWorkflowLayoutService, EmailRegisterWorkflowLayout>();
3232
idServerBuilder.Services.AddTransient<IDataSeeder, InitEmailAuthDataseeder>();
33+
idServerBuilder.Services.AddTransient<IDataSeeder, UpdateEmailTranslationsDataseeder>();
3334
idServerBuilder.Services.AddTransient<IDataSeeder, InitEmailConfigurationDefDataseeder>();
3435
idServerBuilder.AutomaticConfigurationOptions.Add(typeof(IdServerEmailOptions));
3536
if (isDefaultAuthMethod)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
using DataSeeder;
5+
using FormBuilder.Models;
6+
using FormBuilder.Stores;
7+
using SimpleIdServer.IdServer.Stores;
8+
9+
namespace SimpleIdServer.IdServer.Email.Migrations
10+
{
11+
public class UpdateEmailTranslationsDataseeder : BaseAfterDeploymentDataSeeder
12+
{
13+
private readonly ITransactionBuilder _transactionBuilder;
14+
private readonly IFormStore _formStore;
15+
16+
public UpdateEmailTranslationsDataseeder(
17+
ITransactionBuilder transactionBuilder,
18+
IFormStore formStore,
19+
IDataSeederExecutionHistoryRepository dataSeederExecutionHistoryRepository) : base(dataSeederExecutionHistoryRepository)
20+
{
21+
_transactionBuilder = transactionBuilder;
22+
_formStore = formStore;
23+
}
24+
25+
public override string Name => nameof(UpdateEmailTranslationsDataseeder);
26+
27+
protected override async Task Execute(CancellationToken cancellationToken)
28+
{
29+
using (var transaction = _transactionBuilder.Build())
30+
{
31+
await TryUpdate(IdServer.Constants.DefaultRealm, StandardEmailAuthForms.EmailForm, cancellationToken);
32+
await TryUpdate(IdServer.Constants.DefaultRealm, StandardEmailRegistrationForms.EmailForm, cancellationToken);
33+
await transaction.Commit(cancellationToken);
34+
}
35+
}
36+
37+
private async Task TryUpdate(string realm, FormRecord formRecord, CancellationToken cancellationToken)
38+
{
39+
var existingForm = await _formStore.Get(realm, formRecord.Id, cancellationToken);
40+
if (existingForm == null)
41+
{
42+
return;
43+
}
44+
45+
existingForm.SuccessMessageTranslations = formRecord.SuccessMessageTranslations;
46+
existingForm.ErrorMessageTranslations = formRecord.ErrorMessageTranslations;
47+
await _formStore.SaveChanges(cancellationToken);
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)