From ef7932046caad8b01425d1ef69fd3a7bfef61767 Mon Sep 17 00:00:00 2001 From: Carine Dengler Date: Thu, 9 Nov 2023 18:25:27 +0100 Subject: [PATCH 01/11] refactor: use Title object instead of MainTitle, TitleDe and TitleFr --- contracts/evoting/controller/action.go | 6 +- contracts/evoting/types/ballots.go | 15 ++-- contracts/evoting/types/ballots_test.go | 42 +++++------ contracts/evoting/types/election.go | 4 +- integration/integration_test.go | 6 +- integration/performance_test.go | 10 +-- integration/votes_test.go | 4 +- internal/testing/fake/election.go | 16 +++-- proxy/election.go | 2 +- proxy/types/election.go | 2 +- .../src/components/utils/FillFormInfo.tsx | 10 +-- web/frontend/src/mocks/mockData.ts | 42 +++++------ web/frontend/src/mocks/setupMockForms.ts | 4 +- .../pages/ballot/components/BallotDisplay.tsx | 27 ++------ .../src/pages/ballot/components/Rank.tsx | 8 +-- .../src/pages/ballot/components/Select.tsx | 8 +-- .../src/pages/ballot/components/Text.tsx | 6 +- web/frontend/src/pages/form/GroupedResult.tsx | 41 ++++------- .../src/pages/form/IndividualResult.tsx | 41 ++++------- web/frontend/src/pages/form/Result.tsx | 6 +- web/frontend/src/pages/form/Show.tsx | 11 +-- .../form/components/AddQuestionModal.tsx | 20 +++--- .../src/pages/form/components/FormForm.tsx | 32 +++++---- .../src/pages/form/components/FormRow.tsx | 11 +-- .../src/pages/form/components/Question.tsx | 8 +-- .../form/components/SubjectComponent.tsx | 32 +++++---- .../form/components/utils/useQuestionForm.ts | 3 + .../src/schema/configurationValidation.ts | 11 ++- web/frontend/src/schema/form_conf.json | 47 +++++++++++-- web/frontend/src/types/JSONparser.ts | 34 ++------- web/frontend/src/types/configuration.ts | 16 +++-- web/frontend/src/types/form.ts | 6 +- web/frontend/src/types/getObjectType.ts | 69 ++++++++----------- 33 files changed, 272 insertions(+), 328 deletions(-) diff --git a/contracts/evoting/controller/action.go b/contracts/evoting/controller/action.go index bdfaf1315..b0b2a69b6 100644 --- a/contracts/evoting/controller/action.go +++ b/contracts/evoting/controller/action.go @@ -445,7 +445,7 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { return xerrors.Errorf(getFormErr, err) } - dela.Logger.Info().Msg("Title of the form: " + form.Configuration.MainTitle) + dela.Logger.Info().Msg("Title of the form: " + form.Configuration.Title.En) dela.Logger.Info().Msg("Status of the form: " + strconv.Itoa(int(form.Status))) // ###################################### SHUFFLE BALLOTS ################## @@ -642,7 +642,7 @@ func setupSimpleForm(ctx node.Context, secret kyber.Scalar, proxyAddr1 string, return "", types.Form{}, nil, xerrors.Errorf("formID mismatch: %s != %s", form.FormID, formID) } - fmt.Fprintf(ctx.Out, "Title of the form: "+form.Configuration.MainTitle) + fmt.Fprintf(ctx.Out, "Title of the form: "+form.Configuration.Title.En) fmt.Fprintf(ctx.Out, "ID of the form: "+form.FormID) fmt.Fprintf(ctx.Out, "Status of the form: "+strconv.Itoa(int(form.Status))) @@ -650,7 +650,7 @@ func setupSimpleForm(ctx node.Context, secret kyber.Scalar, proxyAddr1 string, } func logFormStatus(form types.Form) { - dela.Logger.Info().Msg("Title of the form : " + form.Configuration.MainTitle) + dela.Logger.Info().Msg("Title of the form : " + form.Configuration.Title.En) dela.Logger.Info().Msg("ID of the form : " + form.FormID) dela.Logger.Info().Msg("Status of the form : " + strconv.Itoa(int(form.Status))) } diff --git a/contracts/evoting/types/ballots.go b/contracts/evoting/types/ballots.go index 17e238a10..00aa39769 100644 --- a/contracts/evoting/types/ballots.go +++ b/contracts/evoting/types/ballots.go @@ -253,12 +253,19 @@ func (b *Ballot) Equal(other Ballot) bool { return true } +// Title contains the titles in different languages. +type Title struct { + En string + Fr string + De string +} + // Subject is a wrapper around multiple questions that can be of type "select", // "rank", or "text". type Subject struct { ID ID - Title string + Title Title // Order defines the order of the different question, which all have a unique // identifier. This is purely for display purpose. @@ -414,7 +421,7 @@ func isValid(q Question) bool { type Select struct { ID ID - Title string + Title Title MaxN uint MinN uint Choices []string @@ -480,7 +487,7 @@ func (s Select) unmarshalAnswers(sforms []string) ([]bool, error) { type Rank struct { ID ID - Title string + Title Title MaxN uint MinN uint Choices []string @@ -553,7 +560,7 @@ func (r Rank) unmarshalAnswers(ranks []string) ([]int8, error) { type Text struct { ID ID - Title string + Title Title MaxN uint MinN uint MaxLength uint diff --git a/contracts/evoting/types/ballots_test.go b/contracts/evoting/types/ballots_test.go index fd64d16db..1a94e6aac 100644 --- a/contracts/evoting/types/ballots_test.go +++ b/contracts/evoting/types/ballots_test.go @@ -56,13 +56,13 @@ func TestBallot_Unmarshal(t *testing.T) { Selects: []Select{{ ID: decodedQuestionID(1), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 2, MinN: 2, Choices: make([]string, 3), }, { ID: decodedQuestionID(2), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 3, MinN: 3, Choices: make([]string, 5), @@ -70,7 +70,7 @@ func TestBallot_Unmarshal(t *testing.T) { Ranks: []Rank{{ ID: decodedQuestionID(3), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 4, MinN: 0, Choices: make([]string, 4), @@ -78,7 +78,7 @@ func TestBallot_Unmarshal(t *testing.T) { Texts: []Text{{ ID: decodedQuestionID(4), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 2, MinN: 2, MaxLength: 10, @@ -305,7 +305,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { subject := Subject{ Subjects: []Subject{{ ID: "", - Title: "", + Title: {En: "", Fr: "", De: ""}, Order: nil, Subjects: []Subject{}, Selects: []Select{}, @@ -315,13 +315,13 @@ func TestSubject_MaxEncodedSize(t *testing.T) { Selects: []Select{{ ID: encodedQuestionID(1), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 3, MinN: 0, Choices: make([]string, 3), }, { ID: encodedQuestionID(2), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 5, MinN: 0, Choices: make([]string, 5), @@ -329,7 +329,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { Ranks: []Rank{{ ID: encodedQuestionID(3), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 4, MinN: 0, Choices: make([]string, 4), @@ -337,7 +337,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { Texts: []Text{{ ID: encodedQuestionID(4), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 2, MinN: 0, MaxLength: 10, @@ -345,7 +345,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { Choices: make([]string, 2), }, { ID: encodedQuestionID(5), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 1, MinN: 0, MaxLength: 10, @@ -355,8 +355,8 @@ func TestSubject_MaxEncodedSize(t *testing.T) { } conf := Configuration{ - MainTitle: "", - Scaffold: []Subject{subject}, + Title: {En: "", Fr: "", De: ""}, + Scaffold: []Subject{subject}, } size := conf.MaxBallotSize() @@ -368,7 +368,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { func TestSubject_IsValid(t *testing.T) { mainSubject := &Subject{ ID: ID(base64.StdEncoding.EncodeToString([]byte("S1"))), - Title: "", + Title: {En: "", Fr: "", De: ""}, Order: []ID{}, Subjects: []Subject{}, Selects: []Select{}, @@ -378,7 +378,7 @@ func TestSubject_IsValid(t *testing.T) { subSubject := &Subject{ ID: ID(base64.StdEncoding.EncodeToString([]byte("S2"))), - Title: "", + Title: {En: "", Fr: "", De: ""}, Order: []ID{}, Subjects: []Subject{}, Selects: []Select{}, @@ -387,8 +387,8 @@ func TestSubject_IsValid(t *testing.T) { } configuration := Configuration{ - MainTitle: "", - Scaffold: []Subject{*mainSubject, *subSubject}, + Title: {En: "", Fr: "", De: ""}, + Scaffold: []Subject{*mainSubject, *subSubject}, } valid := configuration.IsValid() @@ -400,7 +400,7 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Selects = []Select{{ ID: encodedQuestionID(1), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 0, MinN: 0, Choices: make([]string, 0), @@ -408,7 +408,7 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Ranks = []Rank{{ ID: encodedQuestionID(1), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 0, MinN: 0, Choices: make([]string, 0), @@ -423,7 +423,7 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Ranks[0] = Rank{ ID: encodedQuestionID(2), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 0, MinN: 2, Choices: make([]string, 0), @@ -439,7 +439,7 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Ranks = []Rank{} mainSubject.Selects[0] = Select{ ID: encodedQuestionID(1), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 1, MinN: 0, Choices: make([]string, 0), @@ -455,7 +455,7 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Selects = []Select{} mainSubject.Texts = []Text{{ ID: encodedQuestionID(3), - Title: "", + Title: {En: "", Fr: "", De: ""}, MaxN: 2, MinN: 4, MaxLength: 0, diff --git a/contracts/evoting/types/election.go b/contracts/evoting/types/election.go index de35d9630..52caa6c79 100644 --- a/contracts/evoting/types/election.go +++ b/contracts/evoting/types/election.go @@ -197,8 +197,8 @@ type ShuffleInstance struct { // Configuration contains the configuration of a new poll. type Configuration struct { - MainTitle string - Scaffold []Subject + Title Title + Scaffold []Subject } // MaxBallotSize returns the maximum number of bytes required to store a ballot diff --git a/integration/integration_test.go b/integration/integration_test.go index e9a392ba2..274961f8c 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -141,7 +141,7 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { form, err = getForm(formFac, formID, nodes[0].GetOrdering()) require.NoError(t, err) - fmt.Println("Title of the form : " + form.Configuration.MainTitle) + fmt.Println("Title of the form : " + form.Configuration.Title.En) fmt.Println("ID of the form : " + string(form.FormID)) fmt.Println("Status of the form : " + strconv.Itoa(int(form.Status))) fmt.Println("Number of decrypted ballots : " + strconv.Itoa(len(form.DecryptedBallots))) @@ -304,7 +304,7 @@ func getIntegrationTestCrash(numNodes, numVotes, failingNodes int) func(*testing form, err = getForm(formFac, formID, nodes[0].GetOrdering()) require.NoError(t, err) - fmt.Println("Title of the form : " + form.Configuration.MainTitle) + fmt.Println("Title of the form : " + form.Configuration.Title.En) fmt.Println("ID of the form : " + string(form.FormID)) fmt.Println("Status of the form : " + strconv.Itoa(int(form.Status))) fmt.Println("Number of decrypted ballots : " + strconv.Itoa(len(form.DecryptedBallots))) @@ -425,7 +425,7 @@ func getIntegrationBenchmark(numNodes, numVotes int) func(*testing.B) { form, err = getForm(formFac, formID, nodes[0].GetOrdering()) require.NoError(b, err) - fmt.Println("Title of the form : " + form.Configuration.MainTitle) + fmt.Println("Title of the form : " + form.Configuration.Title.En) fmt.Println("ID of the form : " + string(form.FormID)) fmt.Println("Status of the form : " + strconv.Itoa(int(form.Status))) fmt.Println("Number of decrypted ballots : " + strconv.Itoa(len(form.DecryptedBallots))) diff --git a/integration/performance_test.go b/integration/performance_test.go index 5782f34e4..aab340679 100644 --- a/integration/performance_test.go +++ b/integration/performance_test.go @@ -124,7 +124,7 @@ func BenchmarkIntegration_CustomVotesScenario(b *testing.B) { form, err = getForm(formFac, formID, nodes[0].GetOrdering()) require.NoError(b, err) - fmt.Println("Title of the form : " + form.Configuration.MainTitle) + fmt.Println("Title of the form : " + form.Configuration.Title.En) fmt.Println("ID of the form : " + string(form.FormID)) fmt.Println("Status of the form : " + strconv.Itoa(int(form.Status))) fmt.Println("Number of decrypted ballots : " + strconv.Itoa(len(form.DecryptedBallots))) @@ -146,25 +146,25 @@ func BenchmarkIntegration_CustomVotesScenario(b *testing.B) { closeNodesBench(b, nodes) } -func createFormNChunks(m txManager, title string, admin string, numChunks int) ([]byte, error) { +func createFormNChunks(m txManager, title types.Title, admin string, numChunks int) ([]byte, error) { defaultBallotContent := "text:" + encodeID("bb") + ":\n\n" textSize := 29*numChunks - len(defaultBallotContent) // Define the configuration : configuration := types.Configuration{ - MainTitle: title, + Title: title, Scaffold: []types.Subject{ { ID: "aa", - Title: "subject1", + Title: {En: "subject1", Fr: "", De: ""}, Order: nil, Subjects: nil, Selects: nil, Ranks: []types.Rank{}, Texts: []types.Text{{ ID: "bb", - Title: "Enter favorite snack", + Title: {En: "Enter favorite snack", Fr: "", De: ""}, MaxN: 1, MinN: 0, MaxLength: uint(base64.StdEncoding.DecodedLen(textSize)), diff --git a/integration/votes_test.go b/integration/votes_test.go index 1d681c297..53eb765af 100644 --- a/integration/votes_test.go +++ b/integration/votes_test.go @@ -141,7 +141,7 @@ func getIntegrationTestBadVote(numNodes, numVotes, numBadVotes int) func(*testin form, err = getForm(formFac, formID, nodes[0].GetOrdering()) require.NoError(t, err) - fmt.Println("Title of the form : " + form.Configuration.MainTitle) + fmt.Println("Title of the form : " + form.Configuration.Title.En) fmt.Println("ID of the form : " + string(form.FormID)) fmt.Println("Status of the form : " + strconv.Itoa(int(form.Status))) fmt.Println("Number of decrypted ballots : " + strconv.Itoa(len(form.DecryptedBallots))) @@ -281,7 +281,7 @@ func getIntegrationTestRevote(numNodes, numVotes, numRevotes int) func(*testing. form, err = getForm(formFac, formID, nodes[0].GetOrdering()) require.NoError(t, err) - fmt.Println("Title of the form : " + form.Configuration.MainTitle) + fmt.Println("Title of the form : " + form.Configuration.Title.En) fmt.Println("ID of the form : " + string(form.FormID)) fmt.Println("Status of the form : " + strconv.Itoa(int(form.Status))) fmt.Println("Number of decrypted ballots : " + strconv.Itoa(len(form.DecryptedBallots))) diff --git a/internal/testing/fake/election.go b/internal/testing/fake/election.go index d8adc0d0f..5481a3f58 100644 --- a/internal/testing/fake/election.go +++ b/internal/testing/fake/election.go @@ -17,7 +17,11 @@ func NewForm(formID string) types.Form { form := types.Form{ Configuration: types.Configuration{ - MainTitle: "dummyTitle", + Title: types.Title{ + En: "dummyTitle", + Fr: "", + De: "", + }, }, FormID: formID, Status: types.Closed, @@ -68,17 +72,17 @@ func NewKCPointsMarshalled(k int) ([]kyber.Point, []kyber.Point, kyber.Point) { // BasicConfiguration returns a basic form configuration var BasicConfiguration = types.Configuration{ - MainTitle: "formTitle", + Title: types.Title{En: "formTitle", Fr: "", De: ""}, Scaffold: []types.Subject{ { ID: "aa", - Title: "subject1", + Title: types.Title{En: "subject1", Fr: "", De: ""}, Order: nil, Subjects: nil, Selects: []types.Select{ { ID: "bb", - Title: "Select your favorite snacks", + Title: types.Title{En: "Select your favorite snacks", Fr: "", De: ""}, MaxN: 3, MinN: 0, Choices: []string{"snickers", "mars", "vodka", "babibel"}, @@ -89,7 +93,7 @@ var BasicConfiguration = types.Configuration{ }, { ID: "dd", - Title: "subject2", + Title: types.Title{En: "subject2", Fr: "", De: ""}, Order: nil, Subjects: nil, Selects: nil, @@ -97,7 +101,7 @@ var BasicConfiguration = types.Configuration{ Texts: []types.Text{ { ID: "ee", - Title: "dissertation", + Title: types.Title{En: "dissertation", Fr: "", De: ""}, MaxN: 1, MinN: 1, MaxLength: 3, diff --git a/proxy/election.go b/proxy/election.go index e25f5b6ae..8743ae439 100644 --- a/proxy/election.go +++ b/proxy/election.go @@ -482,7 +482,7 @@ func (h *form) Forms(w http.ResponseWriter, r *http.Request) { info := ptypes.LightForm{ FormID: string(form.FormID), - Title: form.Configuration.MainTitle, + Title: form.Configuration.Title, Status: uint16(form.Status), Pubkey: hex.EncodeToString(pubkeyBuf), } diff --git a/proxy/types/election.go b/proxy/types/election.go index 757bd1325..a6a8b5cfc 100644 --- a/proxy/types/election.go +++ b/proxy/types/election.go @@ -54,7 +54,7 @@ type GetFormResponse struct { // LightForm represents a light version of the form type LightForm struct { FormID string - Title string + Title etypes.Title Status uint16 Pubkey string } diff --git a/web/frontend/src/components/utils/FillFormInfo.tsx b/web/frontend/src/components/utils/FillFormInfo.tsx index c397ea614..75b99f93b 100644 --- a/web/frontend/src/components/utils/FillFormInfo.tsx +++ b/web/frontend/src/components/utils/FillFormInfo.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { ID } from 'types/configuration'; +import { ID, Title } from 'types/configuration'; import { FormInfo, LightFormInfo, Results, Status } from 'types/form'; const useFillFormInfo = (formData: FormInfo) => { @@ -52,9 +52,7 @@ const useFillFormInfo = (formData: FormInfo) => { const useFillLightFormInfo = (formData: LightFormInfo) => { const [id, setId] = useState(''); - const [title, setTitle] = useState(''); - const [titleFr, setTitleFr] = useState(''); - const [titleDe, setTitleDe] = useState(''); + const [title, setTitle] = useState({ En: '', Fr: '', De: '' }); const [status, setStatus] = useState<Status>(null); const [pubKey, setPubKey] = useState<string>(''); @@ -64,16 +62,12 @@ const useFillLightFormInfo = (formData: LightFormInfo) => { setTitle(formData.Title); setStatus(formData.Status); setPubKey(formData.Pubkey); - setTitleFr(formData.TitleFr); - setTitleDe(formData.TitleDe); } }, [formData]); return { id, title, - titleFr, - titleDe, status, setStatus, pubKey, diff --git a/web/frontend/src/mocks/mockData.ts b/web/frontend/src/mocks/mockData.ts index 5c7680f4c..70b3ccaa1 100644 --- a/web/frontend/src/mocks/mockData.ts +++ b/web/frontend/src/mocks/mockData.ts @@ -23,17 +23,17 @@ const mockRoster: string[] = [ ]; const mockForm1: any = { - MainTitle: - '{ "en" : "Life on the campus", "fr" : "Vie sur le campus", "de" : "Life on the campus"}', + Title: + { "En" : "Life on the campus", "Fr" : "Vie sur le campus", "De" : "Life on the campus"}, Scaffold: [ { ID: (0xa2ab).toString(), - Title: '{ "en" : "Rate the course", "fr" : "Note la course", "de" : "Rate the course"}', + Title: { "En" : "Rate the course", "Fr" : "Note la course", "De" : "Rate the course"}, Order: [(0x3fb2).toString(), (0x41e2).toString(), (0xcd13).toString(), (0xff31).toString()], Subjects: [ { Title: - '{ "en" : "Let s talk about the food", "fr" : "Parlons de la nourriture", "de" : "Let s talk about food"}', + { "En" : "Let s talk about the food", "Fr" : "Parlons de la nourriture", "De" : "Let s talk about food"}, ID: (0xff31).toString(), Order: [(0xa319).toString(), (0x19c7).toString()], Subjects: [], @@ -41,7 +41,7 @@ const mockForm1: any = { Selects: [ { Title: - '{ "en" : "Select your ingredients", "fr" : "Choisi tes ingrédients", "de" : "Select your ingredients"}', + { "En" : "Select your ingredients", "Fr" : "Choisi tes ingrédients", "De" : "Select your ingredients"}, ID: (0xa319).toString(), MaxN: 2, MinN: 1, @@ -56,7 +56,7 @@ const mockForm1: any = { Ranks: [ { Title: - '{ "en" : "Rank the cafeteria", "fr" : "Ordonne les cafet", "de" : "Rank the cafeteria"}', + { "En" : "Rank the cafeteria", "Fr" : "Ordonne les cafet", "De" : "Rank the cafeteria"}, ID: (0x19c7).toString(), MaxN: 3, MinN: 3, @@ -74,7 +74,7 @@ const mockForm1: any = { Selects: [ { Title: - '{"en" : "How did you find the provided material, from 1 (bad) to 5 (excellent) ?", "fr" : "Comment trouves-tu le matériel fourni, de 1 (mauvais) à 5 (excellent) ?", "de" : "How did you find the provided material, from 1 (bad) to 5 (excellent) ?"}', + {"En" : "How did you find the provided material, from 1 (bad) to 5 (excellent) ?", "Fr" : "Comment trouves-tu le matériel fourni, de 1 (mauvais) à 5 (excellent) ?", "De" : "How did you find the provided material, from 1 (bad) to 5 (excellent) ?"}, ID: (0x3fb2).toString(), MaxN: 1, MinN: 1, @@ -89,7 +89,7 @@ const mockForm1: any = { }, { Title: - '{"en": "How did you find the teaching ?","fr": "Comment trouves-tu l enseignement ?","de": "How did you find the teaching ?"}', + {"En": "How did you find the teaching ?","Fr": "Comment trouves-tu l enseignement ?","De": "How did you find the teaching ?"}, ID: (0x41e2).toString(), MaxN: 1, MinN: 1, @@ -104,7 +104,7 @@ const mockForm1: any = { Texts: [ { Title: - '{ "en" : "Who were the two best TAs ?", "fr" : "Quels sont les deux meilleurs TA ?", "de" : "Who were the two best TAs ?"} ', + { "En" : "Who were the two best TAs ?", "Fr" : "Quels sont les deux meilleurs TA ?", "De" : "Who were the two best TAs ?"} , ID: (0xcd13).toString(), MaxLength: 20, MaxN: 2, @@ -148,18 +148,18 @@ const mockFormResult12: Results = { }; const mockForm2: any = { - MainTitle: - '{"en": "Please give your opinion", "fr": "Donne ton avis", "de": "Please give your opinion"}', + Title: + {"En": "Please give your opinion", "Fr": "Donne ton avis", "De": "Please give your opinion"}, Scaffold: [ { ID: (0xa2ab).toString(), - Title: '{"en": "Rate the course", "fr": "Note le cours", "de": "Rate the course"}', + Title: {"En": "Rate the course", "Fr": "Note le cours", "De": "Rate the course"}, Order: [(0x3fb2).toString(), (0xcd13).toString()], Selects: [ { Title: - '{"en": "How did you find the provided material, from 1 (bad) to 5 (excellent) ?", "fr" : "Comment trouves-tu le matériel fourni, de 1 (mauvais) à 5 (excellent) ?", "de" : "How did you find the provided material, from 1 (bad) to 5 (excellent) ?"}', + {"En": "How did you find the provided material, from 1 (bad) to 5 (excellent) ?", "Fr" : "Comment trouves-tu le matériel fourni, de 1 (mauvais) à 5 (excellent) ?", "De" : "How did you find the provided material, from 1 (bad) to 5 (excellent) ?"}, ID: (0x3fb2).toString(), MaxN: 1, MinN: 1, @@ -176,7 +176,7 @@ const mockForm2: any = { Texts: [ { Title: - '{"en" : "Who were the two best TAs ?", "fr" : "Quels sont les deux meilleurs TA ?", "de" : "Who were the two best TAs ?"}', + {"En" : "Who were the two best TAs ?", "Fr" : "Quels sont les deux meilleurs TA ?", "De" : "Who were the two best TAs ?"}, ID: (0xcd13).toString(), MaxLength: 40, MaxN: 2, @@ -195,12 +195,12 @@ const mockForm2: any = { }, { ID: (0x1234).toString(), - Title: '{"en": "Tough choices", "fr": "Choix difficiles", "de": "Tough choices"}', + Title: {"En": "Tough choices", "Fr": "Choix difficiles", "De": "Tough choices"}, Order: [(0xa319).toString(), (0xcafe).toString(), (0xbeef).toString()], Selects: [ { Title: - '{"en": "Select your ingredients", "fr": "Choisis tes ingrédients", "de": "Select your ingredients"}', + {"En": "Select your ingredients", "Fr": "Choisis tes ingrédients", "De": "Select your ingredients"}, ID: (0xa319).toString(), MaxN: 3, MinN: 0, @@ -217,7 +217,7 @@ const mockForm2: any = { Ranks: [ { Title: - '{"en": "Which cafeteria serves the best coffee ?", "fr": "Quelle cafétéria sert le meilleur café ?", "de": "Which cafeteria serves the best coffee ?"}', + {"En": "Which cafeteria serves the best coffee ?", "Fr": "Quelle cafétéria sert le meilleur café ?", "De": "Which cafeteria serves the best coffee ?"}, ID: (0xcafe).toString(), MaxN: 4, MinN: 1, @@ -230,7 +230,7 @@ const mockForm2: any = { Hint: '{"en": "", "fr": "", "de": ""}', }, { - Title: '{"en": "IN or SC ?", "fr": "IN ou SC ?", "de": "IN or SC ?"}', + Title: {"En": "IN or SC ?", "Fr": "IN ou SC ?", "De": "IN or SC ?"}, ID: (0xbeef).toString(), MaxN: 2, MinN: 1, @@ -245,11 +245,11 @@ const mockForm2: any = { }; const mockForm3: any = { - MainTitle: '{"en": "Lunch", "fr": "Déjeuner", "de": "Lunch"}', + Title: {"En": "Lunch", "Fr": "Déjeuner", "De": "Lunch"}, Scaffold: [ { ID: '3cVHIxpx', - Title: '{"en": "Choose your lunch", "fr": "Choisis ton déjeuner", "de": "Choose your lunch"}', + Title: {"En": "Choose your lunch", "Fr": "Choisis ton déjeuner", "De": "Choose your lunch"}, Order: ['PGP'], Ranks: [], Selects: [], @@ -257,7 +257,7 @@ const mockForm3: any = { { ID: 'PGP', Title: - '{"en": "Select what you want", "fr": "Choisis ce que tu veux", "de": "Select what you want"}', + {"En": "Select what you want", "Fr": "Choisis ce que tu veux", "De": "Select what you want"}, MaxN: 4, MinN: 0, MaxLength: 50, diff --git a/web/frontend/src/mocks/setupMockForms.ts b/web/frontend/src/mocks/setupMockForms.ts index 44e79444b..2ce31cc0a 100644 --- a/web/frontend/src/mocks/setupMockForms.ts +++ b/web/frontend/src/mocks/setupMockForms.ts @@ -99,9 +99,7 @@ const toLightFormInfo = (mockForms: Map<ID, FormInfo>, formID: ID): LightFormInf return { FormID: formID, - Title: form.Configuration.MainTitle, - TitleFr: form.Configuration.TitleFr, - TitleDe: form.Configuration.TitleDe, + Title: form.Configuration.Title, Status: form.Status, Pubkey: form.Pubkey, }; diff --git a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx index bf62f5044..f4d9ff732 100644 --- a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx +++ b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx @@ -5,7 +5,6 @@ import Rank, { handleOnDragEnd } from './Rank'; import Select from './Select'; import Text from './Text'; import { DragDropContext } from 'react-beautiful-dnd'; -import { isJson } from 'types/JSONparser'; type BallotDisplayProps = { configuration: Configuration; @@ -24,18 +23,8 @@ const BallotDisplay: FC<BallotDisplayProps> = ({ }) => { const [titles, setTitles] = useState<any>({}); useEffect(() => { - if (configuration.MainTitle === '') return; - if (isJson(configuration.MainTitle)) { - const ts = JSON.parse(configuration.MainTitle); - setTitles(ts); - } else { - const t = { - en: configuration.MainTitle, - fr: configuration.TitleFr, - de: configuration.TitleDe, - }; - setTitles(t); - } + if (configuration.Title === undefined) return; + setTitles(configuration.Title); }, [configuration]); const SubjectElementDisplay = (element: types.SubjectElement) => { @@ -65,17 +54,13 @@ const BallotDisplay: FC<BallotDisplayProps> = ({ }; const SubjectTree = (subject: types.Subject) => { - let sbj; - if (isJson(subject.Title)) { - sbj = JSON.parse(subject.Title); - } - if (sbj === undefined) sbj = { en: subject.Title, fr: subject.TitleFr, de: subject.TitleDe }; + if (subject.Title === undefined) return; return ( <div key={subject.ID}> <h3 className="text-xl break-all pt-1 pb-1 sm:pt-2 sm:pb-2 border-t font-bold text-gray-600"> - {language === 'en' && sbj.en} - {language === 'fr' && sbj.fr} - {language === 'de' && sbj.de} + {language === 'en' && subject.Title.En} + {language === 'fr' && subject.Title.Fr} + {language === 'de' && subject.Title.De} </h3> {subject.Order.map((id: ID) => ( <div key={id}> diff --git a/web/frontend/src/pages/ballot/components/Rank.tsx b/web/frontend/src/pages/ballot/components/Rank.tsx index 8918b6ccd..1f451c579 100644 --- a/web/frontend/src/pages/ballot/components/Rank.tsx +++ b/web/frontend/src/pages/ballot/components/Rank.tsx @@ -3,7 +3,6 @@ import { Draggable, DropResult, Droppable } from 'react-beautiful-dnd'; import { Answers, ID, RankQuestion } from 'types/configuration'; import { answersFrom } from 'types/getObjectType'; import HintButton from 'components/buttons/HintButton'; -import { isJson } from 'types/JSONparser'; export const handleOnDragEnd = ( result: DropResult, @@ -59,12 +58,7 @@ const Rank: FC<RankProps> = ({ rank, answers, language }) => { }; const [titles, setTitles] = useState<any>({}); useEffect(() => { - if (isJson(rank.Title)) { - const ts = JSON.parse(rank.Title); - setTitles(ts); - } else { - setTitles({ en: rank.Title, fr: rank.TitleFr, de: rank.TitleDe }); - } + setTitles(rank.Title); }, [rank]); const choiceDisplay = (choice: string, rankIndex: number) => { return ( diff --git a/web/frontend/src/pages/ballot/components/Select.tsx b/web/frontend/src/pages/ballot/components/Select.tsx index 32ba57c16..f4006bd36 100644 --- a/web/frontend/src/pages/ballot/components/Select.tsx +++ b/web/frontend/src/pages/ballot/components/Select.tsx @@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next'; import { Answers, SelectQuestion } from 'types/configuration'; import { answersFrom } from 'types/getObjectType'; import HintButton from 'components/buttons/HintButton'; -import { isJson } from 'types/JSONparser'; type SelectProps = { select: SelectQuestion; answers: Answers; @@ -57,12 +56,7 @@ const Select: FC<SelectProps> = ({ select, answers, setAnswers, language }) => { }; const [titles, setTitles] = useState<any>({}); useEffect(() => { - if (isJson(select.Title)) { - const ts = JSON.parse(select.Title); - setTitles(ts); - } else { - setTitles({ en: select.Title, fr: select.TitleFr, de: select.TitleDe }); - } + setTitles(select.Title); }, [select]); const choiceDisplay = (isChecked: boolean, choice: string, choiceIndex: number) => { diff --git a/web/frontend/src/pages/ballot/components/Text.tsx b/web/frontend/src/pages/ballot/components/Text.tsx index 4837cad4e..6a8cf0f29 100644 --- a/web/frontend/src/pages/ballot/components/Text.tsx +++ b/web/frontend/src/pages/ballot/components/Text.tsx @@ -101,9 +101,9 @@ const Text: FC<TextProps> = ({ text, answers, setAnswers, language }) => { <div className="grid grid-rows-1 grid-flow-col"> <div> <h3 className="text-lg break-words text-gray-600 w-96"> - {language === 'en' && text.Title} - {language === 'fr' && text.TitleFr} - {language === 'de' && text.TitleDe} + {language === 'en' && text.Title.En} + {language === 'fr' && text.Title.Fr} + {language === 'de' && text.Title.De} </h3> </div> <div className="text-right"> diff --git a/web/frontend/src/pages/form/GroupedResult.tsx b/web/frontend/src/pages/form/GroupedResult.tsx index cb3015027..e97103baf 100644 --- a/web/frontend/src/pages/form/GroupedResult.tsx +++ b/web/frontend/src/pages/form/GroupedResult.tsx @@ -29,7 +29,6 @@ import { import { default as i18n } from 'i18next'; import SelectResult from './components/SelectResult'; import TextResult from './components/TextResult'; -import { isJson } from 'types/JSONparser'; type GroupedResultProps = { rankResult: RankResults; @@ -53,12 +52,7 @@ const GroupedResult: FC<GroupedResultProps> = ({ rankResult, selectResult, textR const SubjectElementResultDisplay = (element: SubjectElement) => { let titles; - if (isJson(element.Title)) { - titles = JSON.parse(element.Title); - } - if (titles === undefined) { - titles = { en: element.Title, fr: element.TitleFr, de: element.TitleDe }; - } + titles = element.Title; return ( <div className="pl-4 pb-4 sm:pl-6 sm:pb-6"> <div className="flex flex-row"> @@ -66,9 +60,9 @@ const GroupedResult: FC<GroupedResultProps> = ({ rankResult, selectResult, textR {questionIcons[element.Type]} </div> <h2 className="text-lg pb-2"> - {i18n.language === 'en' && titles.en} - {i18n.language === 'fr' && titles.fr} - {i18n.language === 'de' && titles.de} + {i18n.language === 'en' && titles.En} + {i18n.language === 'fr' && titles.Fr} + {i18n.language === 'de' && titles.De} </h2> </div> {element.Type === RANK && rankResult.has(element.ID) && ( @@ -88,19 +82,12 @@ const GroupedResult: FC<GroupedResultProps> = ({ rankResult, selectResult, textR }; const displayResults = (subject: Subject) => { - let sbj; - if (isJson(subject.Title)) { - sbj = JSON.parse(subject.Title); - } - if (sbj === undefined) { - sbj = { en: subject.Title, fr: subject.TitleFr, de: subject.TitleDe }; - } return ( <div key={subject.ID}> <h2 className="text-xl pt-1 pb-1 sm:pt-2 sm:pb-2 border-t font-bold text-gray-600"> - {i18n.language === 'en' && sbj.en} - {i18n.language === 'fr' && sbj.fr} - {i18n.language === 'de' && sbj.de} + {i18n.language === 'en' && subject.Title.En} + {i18n.language === 'fr' && subject.Title.Fr} + {i18n.language === 'de' && subject.Title.De} </h2> {subject.Order.map((id: ID) => ( <div key={id}> @@ -118,7 +105,7 @@ const GroupedResult: FC<GroupedResultProps> = ({ rankResult, selectResult, textR }; const getResultData = (subject: Subject, dataToDownload: DownloadedResults[]) => { - dataToDownload.push({ Title: subject.Title }); + dataToDownload.push({ Title: subject.Title.En }); subject.Order.forEach((id: ID) => { const element = subject.Elements.get(id); @@ -134,7 +121,7 @@ const GroupedResult: FC<GroupedResultProps> = ({ rankResult, selectResult, textR return { Candidate: rank.Choices[index], Percentage: `${percent}%` }; } ); - dataToDownload.push({ Title: element.Title, Results: res }); + dataToDownload.push({ Title: element.Title.En, Results: res }); } break; @@ -145,7 +132,7 @@ const GroupedResult: FC<GroupedResultProps> = ({ rankResult, selectResult, textR res = countSelectResult(selectResult.get(id)).resultsInPercent.map((percent, index) => { return { Candidate: select.Choices[index], Percentage: `${percent}%` }; }); - dataToDownload.push({ Title: element.Title, Results: res }); + dataToDownload.push({ Title: element.Title.En, Results: res }); } break; @@ -158,7 +145,7 @@ const GroupedResult: FC<GroupedResultProps> = ({ rankResult, selectResult, textR res = Array.from(countTextResult(textResult.get(id)).resultsInPercent).map((r) => { return { Candidate: r[0], Percentage: `${r[1]}%` }; }); - dataToDownload.push({ Title: element.Title, Results: res }); + dataToDownload.push({ Title: element.Title.En, Results: res }); } break; } @@ -166,7 +153,7 @@ const GroupedResult: FC<GroupedResultProps> = ({ rankResult, selectResult, textR }; const exportJSONData = () => { - const fileName = `result_${configuration.MainTitle.replace(/[^a-zA-Z0-9]/g, '_').slice( + const fileName = `result_${configuration.Title.En.replace(/[^a-zA-Z0-9]/g, '_').slice( 0, 99 )}__grouped`; // replace spaces with underscores; @@ -178,9 +165,7 @@ const GroupedResult: FC<GroupedResultProps> = ({ rankResult, selectResult, textR }); const data = { - MainTitle: configuration.MainTitle, - TitleFr: configuration.TitleFr, - TitleDe: configuration.TitleDe, + Title: configuration.Title, NumberOfVotes: result.length, Results: dataToDownload, }; diff --git a/web/frontend/src/pages/form/IndividualResult.tsx b/web/frontend/src/pages/form/IndividualResult.tsx index c130e1ca7..14bab6a33 100644 --- a/web/frontend/src/pages/form/IndividualResult.tsx +++ b/web/frontend/src/pages/form/IndividualResult.tsx @@ -31,7 +31,6 @@ import Loading from 'pages/Loading'; import saveAs from 'file-saver'; import { useNavigate } from 'react-router'; import { default as i18n } from 'i18next'; -import { isJson } from 'types/JSONparser'; type IndividualResultProps = { rankResult: RankResults; @@ -70,13 +69,6 @@ const IndividualResult: FC<IndividualResultProps> = ({ [TEXT]: <MenuAlt1Icon />, }; - let titles; - if (isJson(element.Title)) { - titles = JSON.parse(element.Title); - } - if (titles === undefined) { - titles = { en: element.Title, fr: element.TitleFr, de: element.TitleDe }; - } return ( <div className="pl-4 pb-4 sm:pl-6 sm:pb-6"> <div className="flex flex-row"> @@ -84,9 +76,9 @@ const IndividualResult: FC<IndividualResultProps> = ({ {questionIcons[element.Type]} </div> <h2 className="flex align-text-middle text-lg pb-2"> - {i18n.language === 'en' && titles.en} - {i18n.language === 'fr' && titles.fr} - {i18n.language === 'de' && titles.de} + {i18n.language === 'en' && element.Title.En} + {i18n.language === 'fr' && element.Title.Fr} + {i18n.language === 'de' && element.Title.De} </h2> </div> {element.Type === RANK && rankResult.has(element.ID) && ( @@ -115,19 +107,12 @@ const IndividualResult: FC<IndividualResultProps> = ({ const displayResults = useCallback( (subject: Subject) => { - let sbj; - if (isJson(subject.Title)) { - sbj = JSON.parse(subject.Title); - } - if (sbj === undefined) { - sbj = { en: subject.Title, fr: subject.TitleFr, de: subject.TitleDe }; - } return ( <div key={subject.ID}> <h2 className="text-xl pt-1 pb-1 sm:pt-2 sm:pb-2 border-t font-bold text-gray-600"> - {i18n.language === 'en' && sbj.en} - {i18n.language === 'fr' && sbj.fr} - {i18n.language === 'de' && sbj.de} + {i18n.language === 'en' && subject.Title.En} + {i18n.language === 'fr' && subject.Title.Fr} + {i18n.language === 'de' && subject.Title.De} </h2> {subject.Order.map((id: ID) => ( <div key={id}> @@ -151,7 +136,7 @@ const IndividualResult: FC<IndividualResultProps> = ({ dataToDownload: DownloadedResults[], BallotID: number ) => { - dataToDownload.push({ Title: subject.Title }); + dataToDownload.push({ Title: subject.Title.En }); subject.Order.forEach((id: ID) => { const element = subject.Elements.get(id); @@ -168,7 +153,7 @@ const IndividualResult: FC<IndividualResultProps> = ({ Choice: rankQues.Choices[rankResult.get(id)[BallotID].indexOf(index)], }; }); - dataToDownload.push({ Title: element.Title, Results: res }); + dataToDownload.push({ Title: element.Title.En, Results: res }); } break; @@ -180,7 +165,7 @@ const IndividualResult: FC<IndividualResultProps> = ({ const checked = select ? 'True' : 'False'; return { Candidate: selectQues.Choices[index], Checked: checked }; }); - dataToDownload.push({ Title: element.Title, Results: res }); + dataToDownload.push({ Title: element.Title.En, Results: res }); } break; @@ -195,7 +180,7 @@ const IndividualResult: FC<IndividualResultProps> = ({ res = textResult.get(id)[BallotID].map((text, index) => { return { Field: textQues.Choices[index], Answer: text }; }); - dataToDownload.push({ Title: element.Title, Results: res }); + dataToDownload.push({ Title: element.Title.En, Results: res }); } break; } @@ -203,7 +188,7 @@ const IndividualResult: FC<IndividualResultProps> = ({ }; const exportJSONData = () => { - const fileName = `result_${configuration.MainTitle.replace(/[^a-zA-Z0-9]/g, '_').slice( + const fileName = `result_${configuration.Title.En.replace(/[^a-zA-Z0-9]/g, '_').slice( 0, 99 )}__individual`; @@ -219,9 +204,7 @@ const IndividualResult: FC<IndividualResultProps> = ({ }); const data = { - MainTitle: configuration.MainTitle, - TitleFr: configuration.TitleFr, - TitleDe: configuration.TitleDe, + Title: configuration.Title, NumberOfVotes: ballotNumber, Ballots: ballotsToDownload, }; diff --git a/web/frontend/src/pages/form/Result.tsx b/web/frontend/src/pages/form/Result.tsx index 07a6f40cd..582bd66d5 100644 --- a/web/frontend/src/pages/form/Result.tsx +++ b/web/frontend/src/pages/form/Result.tsx @@ -97,9 +97,9 @@ const FormResult: FC = () => { {t('totalNumberOfVotes', { votes: result.length })} </h2> <h3 className="py-6 border-t text-2xl text-center text-gray-700"> - {i18n.language === 'en' && configuration.MainTitle} - {i18n.language === 'fr' && configuration.TitleFr} - {i18n.language === 'de' && configuration.TitleDe} + {i18n.language === 'en' && configuration.Title.En} + {i18n.language === 'fr' && configuration.Title.Fr} + {i18n.language === 'de' && configuration.Title.De} </h3> <div> diff --git a/web/frontend/src/pages/form/Show.tsx b/web/frontend/src/pages/form/Show.tsx index 161fb7f3b..8ca7aa8ab 100644 --- a/web/frontend/src/pages/form/Show.tsx +++ b/web/frontend/src/pages/form/Show.tsx @@ -16,7 +16,6 @@ import UserIDTable from './components/UserIDTable'; import DKGStatusTable from './components/DKGStatusTable'; import LoadingButton from './components/LoadingButton'; import { default as i18n } from 'i18next'; -import { isJson } from 'types/JSONparser'; const FormShow: FC = () => { const { t } = useTranslation(); @@ -202,14 +201,8 @@ const FormShow: FC = () => { const [titles, setTitles] = useState<any>({}); useEffect(() => { try { - if (configObj.MainTitle === '') return; - if (isJson(configObj.MainTitle)) { - const ts = JSON.parse(configObj.MainTitle); - setTitles(ts); - } else { - const tis = { en: configObj.MainTitle, fr: configObj.TitleFr, de: configObj.TitleDe }; - setTitles(tis); - } + if (configObj.Title === undefined) return; + setTitles(configObj.Title); } catch (e) { setError(e.error); } diff --git a/web/frontend/src/pages/form/components/AddQuestionModal.tsx b/web/frontend/src/pages/form/components/AddQuestionModal.tsx index 50ec75bd4..4bc9935be 100644 --- a/web/frontend/src/pages/form/components/AddQuestionModal.tsx +++ b/web/frontend/src/pages/form/components/AddQuestionModal.tsx @@ -45,7 +45,7 @@ const AddQuestionModal: FC<AddQuestionModalProps> = ({ updateChoice, } = useQuestionForm(question); const [language, setLanguage] = useState('en'); - const { Title, TitleDe, TitleFr, MaxN, MinN, ChoicesMap, Hint, HintFr, HintDe } = values; + const { Title, MaxN, MinN, ChoicesMap, Hint, HintFr, HintDe } = values; const [errors, setErrors] = useState([]); const handleSave = async () => { try { @@ -189,9 +189,9 @@ const AddQuestionModal: FC<AddQuestionModalProps> = ({ </label> {language === 'en' && ( <input - value={Title} - onChange={handleChange()} - name="Title" + value={Title.En} + onChange={(e) => handleChange('Title')(e)} + name="En" type="text" placeholder={t('enterTitleLg')} className="my-1 px-1 w-60 ml-1 border rounded-md" @@ -199,9 +199,9 @@ const AddQuestionModal: FC<AddQuestionModalProps> = ({ )} {language === 'fr' && ( <input - value={TitleFr} - onChange={handleChange()} - name="TitleFr" + value={Title.Fr} + onChange={(e) => handleChange('Title')(e)} + name="Fr" type="text" placeholder={t('enterTitleLg1')} className="my-1 px-1 w-60 ml-1 border rounded-md" @@ -209,9 +209,9 @@ const AddQuestionModal: FC<AddQuestionModalProps> = ({ )} {language === 'de' && ( <input - value={TitleDe} - onChange={handleChange()} - name="TitleDe" + value={Title.De} + onChange={(e) => handleChange('Title')(e)} + name="De" type="text" placeholder={t('enterTitleLg2')} className="my-1 px-1 w-60 ml-1 border rounded-md" diff --git a/web/frontend/src/pages/form/components/FormForm.tsx b/web/frontend/src/pages/form/components/FormForm.tsx index 7a21a5ae6..a811b3961 100644 --- a/web/frontend/src/pages/form/components/FormForm.tsx +++ b/web/frontend/src/pages/form/components/FormForm.tsx @@ -38,7 +38,7 @@ import { default as i18n } from 'i18next'; type FormFormProps = {}; const FormForm: FC<FormFormProps> = () => { - // conf is the configuration object containing MainTitle and Scaffold which + // conf is the configuration object containing Title and Scaffold which // contains an array of subject. const { t } = useTranslation(); const emptyConf: Configuration = emptyConfiguration(); @@ -54,7 +54,7 @@ const FormForm: FC<FormFormProps> = () => { const [marshalledConf, setMarshalledConf] = useState<any>(marshalConfig(conf)); const { configuration: previewConf, answers, setAnswers } = useConfiguration(marshalledConf); - const { MainTitle, Scaffold, TitleFr, TitleDe } = conf; + const { Title, Scaffold } = conf; const [language, setLanguage] = useState(i18n.language); const regexPattern = /[^a-zA-Z0-9]/g; @@ -99,6 +99,7 @@ const FormForm: FC<FormFormProps> = () => { try { await configurationSchema.validate(data.Configuration); } catch (err: any) { + console.log(data.Configuration); setTextModal(t('errorIncorrectConfSchema') + err.errors.join(',')); setShowModal(true); return; @@ -145,6 +146,7 @@ const FormForm: FC<FormFormProps> = () => { try { await configurationSchema.validate(data); } catch (err: any) { + console.log(data); setTextModal(t('errorIncorrectConfSchema') + err.errors.join(',')); setShowModal(true); return; @@ -152,7 +154,7 @@ const FormForm: FC<FormFormProps> = () => { const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(JSON.stringify(data))}`; const link = document.createElement('a'); link.href = jsonString; - const title = MainTitle.replace(regexPattern, '_').slice(0, 99); // replace spaces with underscores + const title = Title.En.replace(regexPattern, '_').slice(0, 99); // replace spaces with underscores link.download = title + '.json'; link.click(); }; @@ -192,9 +194,9 @@ const FormForm: FC<FormFormProps> = () => { {language === 'en' && ( <input - value={MainTitle} - onChange={(e) => setConf({ ...conf, MainTitle: e.target.value })} - name="MainTitle" + value={Title.En} + onChange={(e) => setConf({ ...conf, Title: { ...Title, En: e.target.value } })} + name="Title" type="text" placeholder={t('enterMainTitleLg')} className="m-3 px-1 w-100 text-lg border rounded-md" @@ -202,8 +204,8 @@ const FormForm: FC<FormFormProps> = () => { )} {language === 'fr' && ( <input - value={TitleFr} - onChange={(e) => setConf({ ...conf, TitleFr: e.target.value })} + value={Title.Fr} + onChange={(e) => setConf({ ...conf, Title: { ...Title, Fr: e.target.value } })} name="MainTitle1" type="text" placeholder={t('enterMainTitleLg1')} @@ -212,8 +214,8 @@ const FormForm: FC<FormFormProps> = () => { )} {language === 'de' && ( <input - value={TitleDe} - onChange={(e) => setConf({ ...conf, TitleDe: e.target.value })} + value={Title.De} + onChange={(e) => setConf({ ...conf, Title: { ...Title, De: e.target.value } })} name="MainTitle2" type="text" placeholder={t('enterMainTitleLg2')} @@ -223,9 +225,9 @@ const FormForm: FC<FormFormProps> = () => { <div className="ml-1"> <button className={`border p-1 rounded-md ${ - MainTitle.length === 0 ? 'bg-gray-100' : ' ' + Title.En.length === 0 ? 'bg-gray-100' : ' ' }`} - disabled={MainTitle.length === 0} + disabled={Title.En.length === 0} onClick={() => setTitleChanging(false)}> <CheckIcon className="h-5 w-5" aria-hidden="true" /> </button> @@ -236,9 +238,9 @@ const FormForm: FC<FormFormProps> = () => { <div className="mt-1 ml-3 w-[90%] break-words" onClick={() => setTitleChanging(true)}> - {language === 'en' && MainTitle} - {language === 'fr' && TitleFr} - {language === 'de' && TitleDe} + {language === 'en' && Title.En} + {language === 'fr' && Title.Fr} + {language === 'de' && Title.De} </div> <div className="ml-1"> <button diff --git a/web/frontend/src/pages/form/components/FormRow.tsx b/web/frontend/src/pages/form/components/FormRow.tsx index 7c0da7ae9..aaeb76ac9 100644 --- a/web/frontend/src/pages/form/components/FormRow.tsx +++ b/web/frontend/src/pages/form/components/FormRow.tsx @@ -4,7 +4,6 @@ import { Link } from 'react-router-dom'; import FormStatus from './FormStatus'; import QuickAction from './QuickAction'; import { default as i18n } from 'i18next'; -import { isJson } from 'types/JSONparser'; type FormRowProps = { form: LightFormInfo; @@ -13,12 +12,8 @@ type FormRowProps = { const FormRow: FC<FormRowProps> = ({ form }) => { const [titles, setTitles] = useState<any>({}); useEffect(() => { - if (form.Title === '') return; - if (isJson(form.Title)) { - setTitles(JSON.parse(form.Title)); - } else { - setTitles({ en: form.Title, fr: form.TitleFr, de: form.TitleDe }); - } + if (form.Title === undefined) return; + setTitles({ En: form.Title.En, Fr: form.Title.Fr, De: form.Title.De }); }, [form]); // let i18next handle choosing the appropriate language const formRowI18n = i18n.createInstance(); @@ -27,7 +22,7 @@ const FormRow: FC<FormRowProps> = ({ form }) => { formRowI18n.changeLanguage(i18n.language); Object.entries(titles).forEach(([lang, title]: [string, string | undefined]) => { if (title) { - formRowI18n.addResource(lang, 'form', 'title', title); + formRowI18n.addResource(lang.toLowerCase(), 'form', 'title', title); } }); return ( diff --git a/web/frontend/src/pages/form/components/Question.tsx b/web/frontend/src/pages/form/components/Question.tsx index 646d98dd7..539d36984 100644 --- a/web/frontend/src/pages/form/components/Question.tsx +++ b/web/frontend/src/pages/form/components/Question.tsx @@ -15,7 +15,7 @@ type QuestionProps = { }; const Question: FC<QuestionProps> = ({ question, notifyParent, removeQuestion, language }) => { - const { Title, Type, TitleFr, TitleDe } = question; + const { Title, Type } = question; const [openModal, setOpenModal] = useState<boolean>(false); const dropdownContent: { @@ -53,9 +53,9 @@ const Question: FC<QuestionProps> = ({ question, notifyParent, removeQuestion, l <DisplayTypeIcon Type={Type} /> </div> <div className="pt-1.5 max-w-md pr-8 truncate"> - {language === 'en' && (Title.length ? Title : `Enter ${Type} title`)} - {language === 'fr' && (TitleFr.length ? TitleFr : Title)} - {language === 'de' && (TitleDe.length ? TitleDe : Title)} + {language === 'en' && (Title.En.length ? Title.En : `Enter ${Type} title`)} + {language === 'fr' && (Title.Fr.length ? Title.Fr : Title.En)} + {language === 'de' && (Title.De.length ? Title.De : Title.En)} </div> </div> diff --git a/web/frontend/src/pages/form/components/SubjectComponent.tsx b/web/frontend/src/pages/form/components/SubjectComponent.tsx index 6defdf28e..1658fbb06 100644 --- a/web/frontend/src/pages/form/components/SubjectComponent.tsx +++ b/web/frontend/src/pages/form/components/SubjectComponent.tsx @@ -46,7 +46,7 @@ const SubjectComponent: FC<SubjectComponentProps> = ({ const isSubjectMounted = useRef<boolean>(false); const [isOpen, setIsOpen] = useState<boolean>(false); const [titleChanging, setTitleChanging] = useState<boolean>( - subjectObject.Title.length ? false : true + subjectObject.Title.En.length ? false : true ); const [openModal, setOpenModal] = useState<boolean>(false); const [showRemoveElementModal, setShowRemoveElementModal] = useState<boolean>(false); @@ -54,7 +54,7 @@ const SubjectComponent: FC<SubjectComponentProps> = ({ const [elementToRemove, setElementToRemove] = useState(emptyElementToRemove); const [components, setComponents] = useState<ReactElement[]>([]); - const { Title, Order, Elements, TitleFr, TitleDe } = subject; + const { Title, Order, Elements } = subject; // When a property changes, we notify the parent with the new subject object useEffect(() => { // We only notify the parent when the subject is mounted @@ -308,8 +308,10 @@ const SubjectComponent: FC<SubjectComponentProps> = ({ <div className="flex flex-col mt-3 mb-2"> {language === 'en' && ( <input - value={Title} - onChange={(e) => setSubject({ ...subject, Title: e.target.value })} + value={Title.En} + onChange={(e) => + setSubject({ ...subject, Title: { ...Title, En: e.target.value } }) + } name="Title" type="text" placeholder={t('enterSubjectTitleLg')} @@ -320,8 +322,10 @@ const SubjectComponent: FC<SubjectComponentProps> = ({ )} {language === 'fr' && ( <input - value={TitleFr} - onChange={(e) => setSubject({ ...subject, TitleFr: e.target.value })} + value={Title.Fr} + onChange={(e) => + setSubject({ ...subject, Title: { ...Title, Fr: e.target.value } }) + } name="Title" type="text" placeholder={t('enterSubjectTitleLg1')} @@ -332,8 +336,10 @@ const SubjectComponent: FC<SubjectComponentProps> = ({ )} {language === 'de' && ( <input - value={TitleDe} - onChange={(e) => setSubject({ ...subject, TitleDe: e.target.value })} + value={Title.De} + onChange={(e) => + setSubject({ ...subject, Title: { ...Title, De: e.target.value } }) + } name="Title" type="text" placeholder={t('enterSubjectTitleLg2')} @@ -344,8 +350,8 @@ const SubjectComponent: FC<SubjectComponentProps> = ({ )} <div className="ml-1"> <button - className={`border p-1 rounded-md ${Title.length === 0 && 'bg-gray-100'}`} - disabled={Title.length === 0} + className={`border p-1 rounded-md ${Title.En.length === 0 && 'bg-gray-100'}`} + disabled={Title.En.length === 0} onClick={() => setTitleChanging(false)}> <CheckIcon className="h-5 w-5" aria-hidden="true" /> </button> @@ -354,9 +360,9 @@ const SubjectComponent: FC<SubjectComponentProps> = ({ ) : ( <div className="flex mb-2 max-w-md truncate"> <div className="pt-1.5 truncate" onClick={() => setTitleChanging(true)}> - {language === 'en' && Title} - {language === 'fr' && TitleFr} - {language === 'de' && TitleDe} + {language === 'en' && Title.En} + {language === 'fr' && Title.Fr} + {language === 'de' && Title.De} </div> <div className="ml-1 pr-10"> <button diff --git a/web/frontend/src/pages/form/components/utils/useQuestionForm.ts b/web/frontend/src/pages/form/components/utils/useQuestionForm.ts index 913082fee..2fbcc7971 100644 --- a/web/frontend/src/pages/form/components/utils/useQuestionForm.ts +++ b/web/frontend/src/pages/form/components/utils/useQuestionForm.ts @@ -20,6 +20,9 @@ const useQuestionForm = (initState: RankQuestion | SelectQuestion | TextQuestion newChoicesMap.set('fr', [...newChoicesMap.get('fr'), '']); newChoicesMap.set('de', [...newChoicesMap.get('de'), '']); switch (Exception) { + case 'Title': + setState({ ...state, Title: { ...state.Title, [name]: value } }); + break; case 'RankMinMax': setState({ ...state, MinN: Number(value), MaxN: Number(value) }); break; diff --git a/web/frontend/src/schema/configurationValidation.ts b/web/frontend/src/schema/configurationValidation.ts index 07b8f64ea..35130a6fb 100644 --- a/web/frontend/src/schema/configurationValidation.ts +++ b/web/frontend/src/schema/configurationValidation.ts @@ -1,11 +1,10 @@ import * as yup from 'yup'; const idSchema = yup.string().min(1).required(); -const titleSchema = yup.string().required(); -const formTitleSchema = yup.object({ - en: yup.string().required(), - fr: yup.string(), - de: yup.string(), +const titleSchema = yup.object({ + En: yup.string().required(), + Fr: yup.string(), + De: yup.string(), }); const selectsSchema = yup.object({ @@ -408,7 +407,7 @@ const subjectSchema = yup.object({ }); const configurationSchema = yup.object({ - MainTitle: yup.lazy(() => formTitleSchema), + Title: yup.lazy(() => titleSchema), Scaffold: yup.array().of(subjectSchema).required(), }); diff --git a/web/frontend/src/schema/form_conf.json b/web/frontend/src/schema/form_conf.json index 0569bc2d7..4cba34764 100644 --- a/web/frontend/src/schema/form_conf.json +++ b/web/frontend/src/schema/form_conf.json @@ -2,14 +2,28 @@ "$id": "configurationSchema", "type": "object", "properties": { - "MainTitle": { "type": "string" }, + "Title": { + "type": "object", + "properties": { + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } + } + }, "Scaffold": { "type": "array", "items": { "type": "object", "properties": { "ID": { "type": "string" }, - "Title": { "type": "string" }, + "Title": { + "type": "object", + "properties": { + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } + } + }, "Order": { "type": "array", "items": { "type": "string" } @@ -24,7 +38,14 @@ "type": "object", "properties": { "ID": { "type": "string" }, - "Title": { "type": "string" }, + "Title": { + "type": "object", + "properties": { + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } + } + }, "MaxN": { "type": "number" }, "MinN": { "type": "number" }, "Choices": { @@ -43,7 +64,14 @@ "type": "object", "properties": { "ID": { "type": "string" }, - "Title": { "type": "string" }, + "Title": { + "type": "object", + "properties": { + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } + } + }, "MaxN": { "type": "number" }, "MinN": { "type": "number" }, "Choices": { @@ -62,7 +90,14 @@ "type": "object", "properties": { "ID": { "type": "string" }, - "Title": { "type": "string" }, + "Title": { + "type": "object", + "properties": { + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } + } + }, "MaxN": { "type": "number" }, "MinN": { "type": "number" }, "Regex": { "type": "string" }, @@ -83,6 +118,6 @@ } } }, - "required": ["MainTitle", "Scaffold"], + "required": ["Title", "Scaffold"], "additionalProperties": false } diff --git a/web/frontend/src/types/JSONparser.ts b/web/frontend/src/types/JSONparser.ts index b40daea98..fde3b18fe 100644 --- a/web/frontend/src/types/JSONparser.ts +++ b/web/frontend/src/types/JSONparser.ts @@ -15,7 +15,6 @@ const isJson = (str: string) => { }; const unmarshalText = (text: any): types.TextQuestion => { const t = text as types.TextQuestion; - const titles = JSON.parse(t.Title); if (t.Hint === undefined) { t.Hint = JSON.stringify({ en: '', @@ -26,9 +25,7 @@ const unmarshalText = (text: any): types.TextQuestion => { const hint = JSON.parse(t.Hint); return { ...text, - Title: titles.en, - TitleFr: titles.fr, - TitleDe: titles.de, + Title: t.Title, Hint: hint.en, HintFr: hint.fr, HintDe: hint.de, @@ -39,7 +36,6 @@ const unmarshalText = (text: any): types.TextQuestion => { const unmarshalRank = (rank: any): types.RankQuestion => { const r = rank as types.RankQuestion; - const titles = JSON.parse(r.Title); if (r.Hint === undefined) { r.Hint = JSON.stringify({ en: '', @@ -50,8 +46,7 @@ const unmarshalRank = (rank: any): types.RankQuestion => { const hint = JSON.parse(r.Hint); return { ...rank, - TitleFr: titles.fr, - TitleDe: titles.de, + Title: r.Title, Hint: hint.en, HintFr: hint.fr, HintDe: hint.de, @@ -62,7 +57,6 @@ const unmarshalRank = (rank: any): types.RankQuestion => { const unmarshalSelect = (select: any): types.SelectQuestion => { const s = select as types.SelectQuestion; - const titles = JSON.parse(s.Title); if (s.Hint === undefined) { s.Hint = JSON.stringify({ en: '', @@ -73,8 +67,7 @@ const unmarshalSelect = (select: any): types.SelectQuestion => { const hint = JSON.parse(s.Hint); return { ...select, - TitleFr: titles.fr, - TitleDe: titles.de, + Title: s.Title, Hint: hint.en, HintFr: hint.fr, HintDe: hint.de, @@ -168,20 +161,8 @@ const unmarshalSubjectAndCreateAnswers = ( }; const unmarshalConfig = (json: any): types.Configuration => { - let titles; - if (isJson(json.MainTitle)) titles = JSON.parse(json.MainTitle); - else { - titles = { - en: json.MainTitle, - fr: json.TitleFr, - de: json.TitleDe, - }; - } - const conf = { - MainTitle: titles.en, - TitleFr: titles.fr, - TitleDe: titles.de, + Title: json.Title, Scaffold: [], }; for (const subject of json.Scaffold) { @@ -247,12 +228,7 @@ const marshalSubject = (subject: types.Subject): any => { }; const marshalConfig = (configuration: types.Configuration): any => { - const title = { - en: configuration.MainTitle, - fr: configuration.TitleFr, - de: configuration.TitleDe, - }; - const conf = { MainTitle: JSON.stringify(title), Scaffold: [] }; + const conf = { Title: configuration.Title, Scaffold: [] }; for (const subject of configuration.Scaffold) { conf.Scaffold.push(marshalSubject(subject)); } diff --git a/web/frontend/src/types/configuration.ts b/web/frontend/src/types/configuration.ts index b28fa6e14..3fce041ed 100644 --- a/web/frontend/src/types/configuration.ts +++ b/web/frontend/src/types/configuration.ts @@ -5,12 +5,17 @@ export const SELECT: string = 'select'; export const SUBJECT: string = 'subject'; export const TEXT: string = 'text'; +// Title +interface Title { + En: string; + Fr: string; + De: string; +} + interface SubjectElement { ID: ID; Type: string; - Title: string; - TitleFr: string; - TitleDe: string; + Title: Title; } // Rank describes a "rank" question, which requires the user to rank choices. @@ -55,10 +60,8 @@ interface Subject extends SubjectElement { // Configuration contains the configuration of a new poll. interface Configuration { - MainTitle: string; + Title: Title; Scaffold: Subject[]; - TitleFr: string; - TitleDe: string; } // Answers describes the current answers for each type of question @@ -72,6 +75,7 @@ interface Answers { export type { ID, + Title, TextQuestion, SelectQuestion, RankQuestion, diff --git a/web/frontend/src/types/form.ts b/web/frontend/src/types/form.ts index 1aa99b7d6..6ea5a80dd 100644 --- a/web/frontend/src/types/form.ts +++ b/web/frontend/src/types/form.ts @@ -1,4 +1,4 @@ -import { ID } from './configuration'; +import { ID, Title } from './configuration'; export const enum Status { // Initial is when the form has just been created @@ -58,9 +58,7 @@ interface FormInfo { interface LightFormInfo { FormID: ID; - Title: string; - TitleFr: string; - TitleDe: string; + Title: Title; Status: Status; Pubkey: string; } diff --git a/web/frontend/src/types/getObjectType.ts b/web/frontend/src/types/getObjectType.ts index b244e4753..c72739b17 100644 --- a/web/frontend/src/types/getObjectType.ts +++ b/web/frontend/src/types/getObjectType.ts @@ -6,19 +6,23 @@ const uid: Function = new ShortUniqueId({ length: 8 }); const emptyConfiguration = (): types.Configuration => { return { - MainTitle: '', + Title: { + En: '', + Fr: '', + De: '', + }, Scaffold: [], - TitleFr: '', - TitleDe: '', }; }; const newSubject = (): types.Subject => { return { ID: uid(), - Title: '', - TitleFr: '', - TitleDe: '', + Title: { + En: '', + Fr: '', + De: '', + }, Order: [], Type: SUBJECT, Elements: new Map(), @@ -28,9 +32,11 @@ const obj = { en: [''], fr: [''], de: [''] }; const newRank = (): types.RankQuestion => { return { ID: uid(), - Title: '', - TitleFr: '', - TitleDe: '', + Title: { + En: '', + Fr: '', + De: '', + }, MaxN: 2, MinN: 2, Choices: [], @@ -45,9 +51,11 @@ const newRank = (): types.RankQuestion => { const newSelect = (): types.SelectQuestion => { return { ID: uid(), - Title: '', - TitleDe: '', - TitleFr: '', + Title: { + En: '', + Fr: '', + De: '', + }, MaxN: 1, MinN: 1, Choices: [], @@ -62,9 +70,11 @@ const newSelect = (): types.SelectQuestion => { const newText = (): types.TextQuestion => { return { ID: uid(), - Title: '', - TitleFr: '', - TitleDe: '', + Title: { + En: '', + Fr: '', + De: '', + }, MaxN: 1, MinN: 0, MaxLength: 50, @@ -142,16 +152,10 @@ const toArraysOfSubjectElement = ( const selectQuestion: types.SelectQuestion[] = []; const textQuestion: types.TextQuestion[] = []; const subjects: types.Subject[] = []; - let title = ''; let hint = ''; elements.forEach((element) => { switch (element.Type) { case RANK: - title = JSON.stringify({ - en: element.Title, - fr: element.TitleFr, - de: element.TitleDe, - }); hint = JSON.stringify({ en: (element as types.RankQuestion).Hint, fr: (element as types.RankQuestion).HintFr, @@ -159,7 +163,7 @@ const toArraysOfSubjectElement = ( }); rankQuestion.push({ ...(element as types.RankQuestion), - Title: title, + Title: element.Title, Choices: choicesMapToChoices( (element as types.RankQuestion).ChoicesMap ), @@ -167,11 +171,6 @@ const toArraysOfSubjectElement = ( }); break; case SELECT: - title = JSON.stringify({ - en: element.Title, - fr: element.TitleFr, - de: element.TitleDe, - }); hint = JSON.stringify({ en: (element as types.SelectQuestion).Hint, fr: (element as types.SelectQuestion).HintFr, @@ -179,7 +178,7 @@ const toArraysOfSubjectElement = ( }); selectQuestion.push({ ...(element as types.SelectQuestion), - Title: title, + Title: element.Title, Choices: choicesMapToChoices( (element as types.SelectQuestion).ChoicesMap ), @@ -187,11 +186,6 @@ const toArraysOfSubjectElement = ( }); break; case TEXT: - title = JSON.stringify({ - en: element.Title, - fr: element.TitleFr, - de: element.TitleDe, - }); hint = JSON.stringify({ en: (element as types.TextQuestion).Hint, fr: (element as types.TextQuestion).HintFr, @@ -199,7 +193,7 @@ const toArraysOfSubjectElement = ( }); textQuestion.push({ ...(element as types.TextQuestion), - Title: title, + Title: element.Title, Choices: choicesMapToChoices( (element as types.TextQuestion).ChoicesMap ), @@ -207,14 +201,9 @@ const toArraysOfSubjectElement = ( }); break; case SUBJECT: - title = JSON.stringify({ - en: element.Title, - fr: element.TitleFr, - de: element.TitleDe, - }); subjects.push({ ...(element as types.Subject), - Title: title, + Title: element.Title, }); break; } From 7a75dcf9151c312a67c837514ce57f3999d37380 Mon Sep 17 00:00:00 2001 From: Carine Dengler <git@carine-dengler.de> Date: Tue, 21 Nov 2023 10:05:52 +0100 Subject: [PATCH 02/11] refactor: use/clean up Hint object --- contracts/evoting/types/ballots.go | 13 ++++-- web/frontend/src/mocks/mockData.ts | 22 ++++----- .../src/pages/ballot/components/Rank.tsx | 6 +-- .../src/pages/ballot/components/Select.tsx | 6 +-- .../src/pages/ballot/components/Text.tsx | 6 +-- .../form/components/AddQuestionModal.tsx | 20 ++++----- .../form/components/utils/useQuestionForm.ts | 3 ++ .../src/schema/configurationValidation.ts | 11 +++-- web/frontend/src/schema/form_conf.json | 27 +++++++++-- web/frontend/src/types/JSONparser.ts | 45 ++++++++----------- web/frontend/src/types/configuration.ts | 20 +++++---- web/frontend/src/types/getObjectType.ts | 42 +++++++---------- 12 files changed, 119 insertions(+), 102 deletions(-) diff --git a/contracts/evoting/types/ballots.go b/contracts/evoting/types/ballots.go index 00aa39769..907678a2f 100644 --- a/contracts/evoting/types/ballots.go +++ b/contracts/evoting/types/ballots.go @@ -260,6 +260,13 @@ type Title struct { De string } +// Hint contains explanations in different languages. +type Hint struct { + En string + Fr string + De string +} + // Subject is a wrapper around multiple questions that can be of type "select", // "rank", or "text". type Subject struct { @@ -425,7 +432,7 @@ type Select struct { MaxN uint MinN uint Choices []string - Hint string + Hint Hint } // GetID implements Question @@ -491,7 +498,7 @@ type Rank struct { MaxN uint MinN uint Choices []string - Hint string + Hint Hint } func (r Rank) GetID() string { @@ -566,7 +573,7 @@ type Text struct { MaxLength uint Regex string Choices []string - Hint string + Hint Hint } func (t Text) GetID() string { diff --git a/web/frontend/src/mocks/mockData.ts b/web/frontend/src/mocks/mockData.ts index 70b3ccaa1..5a2a6c5e8 100644 --- a/web/frontend/src/mocks/mockData.ts +++ b/web/frontend/src/mocks/mockData.ts @@ -50,7 +50,7 @@ const mockForm1: any = { '{"en": "salad", "fr": "salade", "de": "salad"}', '{"en": "onion", "fr": "oignon", "de": "onion"}', ], - Hint: '{"en": "", "fr": "", "de": ""}', + Hint: {"En": "", "Fr": "", "De": ""}, }, ], Ranks: [ @@ -65,7 +65,7 @@ const mockForm1: any = { '{"en": "SV", "fr": "SV", "de": "SV"}', '{"en": "Parmentier", "fr": "Parmentier", "de": "Parmentier"}', ], - Hint: '{"en": "", "fr": "", "de": ""}', + Hint: {"En": "", "Fr": "", "De": ""}, }, ], }, @@ -85,7 +85,7 @@ const mockForm1: any = { '{"en":"4", "fr": "4", "de": "4"}', '{ "en": "5", "fr": "5", "de": "5" }', ], - Hint: '{"en": "", "fr": "", "de": ""}', + Hint: {"En": "", "Fr": "", "De": ""}, }, { Title: @@ -98,7 +98,7 @@ const mockForm1: any = { '{"en" : "normal", "fr": "normal", "de": "normal"}', '{"en" : "good", "fr": "super", "de": "good"}', ], - Hint: '{"en": "Be honest. This is anonymous anyway", "fr": "Sois honnête. C est anonyme de toute façon", "de": "Be honest. This is anonymous anyway"}', + Hint: {"En": "Be honest. This is anonymous anyway", "Fr": "Sois honnête. C est anonyme de toute façon", "De": "Be honest. This is anonymous anyway"}, }, ], Texts: [ @@ -114,7 +114,7 @@ const mockForm1: any = { '{"en":"TA1", "fr": "TA1", "de": "TA1"}', '{"en":"TA2", "fr":"TA2","de": "TA2"}', ], - Hint: '{"en": "", "fr": "", "de": ""}', + Hint: {"En": "", "Fr": "", "De": ""}, }, ], }, @@ -170,7 +170,7 @@ const mockForm2: any = { '{"en":"4", "fr": "4", "de": "4"}', '{ "en": "5", "fr": "5", "de": "5" }', ], - Hint: '{"en": "", "fr": "", "de": ""}', + Hint: {"En": "", "Fr": "", "De": ""}, }, ], Texts: [ @@ -186,7 +186,7 @@ const mockForm2: any = { '{"en":"TA2", "fr":"TA2","de": "TA2"}', ], Regex: '^[A-Z][a-z]+$', - Hint: '{"en": "", "fr": "", "de": ""}', + Hint: {"En": "", "Fr": "", "De": ""}, }, ], @@ -210,7 +210,7 @@ const mockForm2: any = { '{"en": "onion", "fr": "oignon", "de": "onion"}', '{"en": "falafel", "fr": "falafel", "de": "falafel"}', ], - Hint: '{"en": "", "fr": "", "de": ""}', + Hint: {"En": "", "Fr": "", "De": ""}, }, ], @@ -227,7 +227,7 @@ const mockForm2: any = { '{"en": "Arcadie", "fr": "Arcadie", "de": "Arcadie"}', '{"en": "Montreux Jazz Cafe", "fr": "Montreux Jazz Cafe", "de": "Montreux Jazz Cafe"}', ], - Hint: '{"en": "", "fr": "", "de": ""}', + Hint: {"En": "", "Fr": "", "De": ""}, }, { Title: {"En": "IN or SC ?", "Fr": "IN ou SC ?", "De": "IN or SC ?"}, @@ -235,7 +235,7 @@ const mockForm2: any = { MaxN: 2, MinN: 1, Choices: ['{"en": "IN", "fr": "IN", "de": "IN"}', '{"en": "SC", "fr": "SC", "de": "SC"}'], - Hint: '{"en": "The right answer is IN ;-)", "fr": "La bonne réponse est IN ;-)", "de": "The right answer is IN ;-)"}', + Hint: {"En": "The right answer is IN ;-)", "Fr": "La bonne réponse est IN ;-)", "De": "The right answer is IN ;-)"}, }, ], Texts: [], @@ -268,7 +268,7 @@ const mockForm3: any = { '{"en": "Drink 🧃", "fr": "Boisson 🧃", "de": "Drink 🧃"}', '{"en":"Dessert 🍰", "fr": "Dessert 🍰", "de": "Dessert 🍰"}', ], - Hint: '{"en": "If you change opinion call me before 11:30 a.m.", "fr": "Si tu changes d\'avis appelle moi avant 11h30", "de": "If you change opinion call me before 11:30 a.m."}', + Hint: {"En": "If you change opinion call me before 11:30 a.m.", "Fr": "Si tu changes d\avis appelle moi avant 11h30", "De": "If you change opinion call me before 11:30 a.m."}, }, ], Subjects: [], diff --git a/web/frontend/src/pages/ballot/components/Rank.tsx b/web/frontend/src/pages/ballot/components/Rank.tsx index 1f451c579..437671384 100644 --- a/web/frontend/src/pages/ballot/components/Rank.tsx +++ b/web/frontend/src/pages/ballot/components/Rank.tsx @@ -93,9 +93,9 @@ const Rank: FC<RankProps> = ({ rank, answers, language }) => { </h3> </div> <div className="text-right"> - {language === 'en' && <HintButton text={rank.Hint} />} - {language === 'fr' && <HintButton text={rank.HintFr} />} - {language === 'de' && <HintButton text={rank.HintDe} />} + {language === 'en' && <HintButton text={rank.Hint.En} />} + {language === 'fr' && <HintButton text={rank.Hint.Fr} />} + {language === 'de' && <HintButton text={rank.Hint.De} />} </div> </div> <div className="mt-5 px-4 max-w-[300px] sm:pl-8 sm:max-w-md"> diff --git a/web/frontend/src/pages/ballot/components/Select.tsx b/web/frontend/src/pages/ballot/components/Select.tsx index f4006bd36..f93dfead9 100644 --- a/web/frontend/src/pages/ballot/components/Select.tsx +++ b/web/frontend/src/pages/ballot/components/Select.tsx @@ -87,9 +87,9 @@ const Select: FC<SelectProps> = ({ select, answers, setAnswers, language }) => { </h3> </div> <div className="text-right"> - {language === 'en' && <HintButton text={select.Hint} />} - {language === 'fr' && <HintButton text={select.HintFr} />} - {language === 'de' && <HintButton text={select.HintDe} />} + {language === 'en' && <HintButton text={select.Hint.En} />} + {language === 'fr' && <HintButton text={select.Hint.Fr} />} + {language === 'de' && <HintButton text={select.Hint.De} />} </div> </div> <div className="pt-1">{requirementsDisplay()}</div> diff --git a/web/frontend/src/pages/ballot/components/Text.tsx b/web/frontend/src/pages/ballot/components/Text.tsx index 6a8cf0f29..d065723f5 100644 --- a/web/frontend/src/pages/ballot/components/Text.tsx +++ b/web/frontend/src/pages/ballot/components/Text.tsx @@ -107,9 +107,9 @@ const Text: FC<TextProps> = ({ text, answers, setAnswers, language }) => { </h3> </div> <div className="text-right"> - {language === 'en' && <HintButton text={text.Hint} />} - {language === 'fr' && <HintButton text={text.HintFr} />} - {language === 'de' && <HintButton text={text.HintDe} />} + {language === 'en' && <HintButton text={text.Hint.En} />} + {language === 'fr' && <HintButton text={text.Hint.Fr} />} + {language === 'de' && <HintButton text={text.Hint.De} />} </div> </div> <div className="pt-1">{requirementsDisplay()}</div> diff --git a/web/frontend/src/pages/form/components/AddQuestionModal.tsx b/web/frontend/src/pages/form/components/AddQuestionModal.tsx index 4bc9935be..228bc97ab 100644 --- a/web/frontend/src/pages/form/components/AddQuestionModal.tsx +++ b/web/frontend/src/pages/form/components/AddQuestionModal.tsx @@ -45,7 +45,7 @@ const AddQuestionModal: FC<AddQuestionModalProps> = ({ updateChoice, } = useQuestionForm(question); const [language, setLanguage] = useState('en'); - const { Title, MaxN, MinN, ChoicesMap, Hint, HintFr, HintDe } = values; + const { Title, MaxN, MinN, ChoicesMap, Hint } = values; const [errors, setErrors] = useState([]); const handleSave = async () => { try { @@ -231,9 +231,9 @@ const AddQuestionModal: FC<AddQuestionModalProps> = ({ </label> {language === 'en' && ( <input - value={Hint} - onChange={handleChange()} - name="Hint" + value={Hint.En} + onChange={(e) => handleChange('Hint')(e)} + name="En" type="text" placeholder={t('enterHintLg')} className="my-1 px-1 w-60 ml-1 border rounded-md" @@ -241,9 +241,9 @@ const AddQuestionModal: FC<AddQuestionModalProps> = ({ )} {language === 'fr' && ( <input - value={HintFr} - onChange={handleChange()} - name="HintFr" + value={Hint.Fr} + onChange={(e) => handleChange('Hint')(e)} + name="Fr" type="text" placeholder={t('enterHintLg1')} className="my-1 px-1 w-60 ml-1 border rounded-md" @@ -251,9 +251,9 @@ const AddQuestionModal: FC<AddQuestionModalProps> = ({ )} {language === 'de' && ( <input - value={HintDe} - onChange={handleChange()} - name="HintDe" + value={Hint.De} + onChange={(e) => handleChange('Hint')(e)} + name="De" type="text" placeholder={t('enterHintLg2')} className="my-1 px-1 w-60 ml-1 border rounded-md" diff --git a/web/frontend/src/pages/form/components/utils/useQuestionForm.ts b/web/frontend/src/pages/form/components/utils/useQuestionForm.ts index 2fbcc7971..576012568 100644 --- a/web/frontend/src/pages/form/components/utils/useQuestionForm.ts +++ b/web/frontend/src/pages/form/components/utils/useQuestionForm.ts @@ -23,6 +23,9 @@ const useQuestionForm = (initState: RankQuestion | SelectQuestion | TextQuestion case 'Title': setState({ ...state, Title: { ...state.Title, [name]: value } }); break; + case 'Hint': + setState({ ...state, Hint: { ...state.Hint, [name]: value } }); + break; case 'RankMinMax': setState({ ...state, MinN: Number(value), MaxN: Number(value) }); break; diff --git a/web/frontend/src/schema/configurationValidation.ts b/web/frontend/src/schema/configurationValidation.ts index 35130a6fb..c6c73fa15 100644 --- a/web/frontend/src/schema/configurationValidation.ts +++ b/web/frontend/src/schema/configurationValidation.ts @@ -6,6 +6,11 @@ const titleSchema = yup.object({ Fr: yup.string(), De: yup.string(), }); +const hintSchema = yup.object({ + En: yup.string(), + Fr: yup.string(), + De: yup.string(), +}); const selectsSchema = yup.object({ ID: yup.lazy(() => idSchema), @@ -103,7 +108,7 @@ const selectsSchema = yup.object({ }, }) .required(), - Hint: yup.lazy(() => yup.string()), + Hint: yup.lazy(() => hintSchema), }); const ranksSchema = yup.object({ @@ -201,7 +206,7 @@ const ranksSchema = yup.object({ }, }) .required(), - Hint: yup.lazy(() => yup.string()), + Hint: yup.lazy(() => hintSchema), }); const textsSchema = yup.object({ @@ -321,7 +326,7 @@ const textsSchema = yup.object({ }, }) .required(), - Hint: yup.lazy(() => yup.string()), + Hint: yup.lazy(() => hintSchema), }); const subjectSchema = yup.object({ diff --git a/web/frontend/src/schema/form_conf.json b/web/frontend/src/schema/form_conf.json index 4cba34764..be2cff897 100644 --- a/web/frontend/src/schema/form_conf.json +++ b/web/frontend/src/schema/form_conf.json @@ -52,7 +52,14 @@ "type": "array", "items": { "type": "string" } }, - "Hint": { "type": "string" } + "Hint": { + "type": "object", + "properties": { + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } + } + } }, "required": ["ID", "Title", "MaxN", "MinN", "Choices"], "additionalProperties": false @@ -78,7 +85,14 @@ "type": "array", "items": { "type": "string" } }, - "Hint": { "type": "string" } + "Hint": { + "type": "object", + "properties": { + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } + } + } }, "required": ["ID", "Title", "MaxN", "MinN", "Choices"], "additionalProperties": false @@ -106,7 +120,14 @@ "type": "array", "items": { "type": "string" } }, - "Hint": { "type": "string" } + "Hint": { + "type": "object", + "properties": { + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } + } + } }, "required": ["ID", "Title", "MaxN", "MinN", "Regex", "MaxLength", "Choices"], "additionalProperties": false diff --git a/web/frontend/src/types/JSONparser.ts b/web/frontend/src/types/JSONparser.ts index fde3b18fe..2b9ba1ea9 100644 --- a/web/frontend/src/types/JSONparser.ts +++ b/web/frontend/src/types/JSONparser.ts @@ -16,19 +16,16 @@ const isJson = (str: string) => { const unmarshalText = (text: any): types.TextQuestion => { const t = text as types.TextQuestion; if (t.Hint === undefined) { - t.Hint = JSON.stringify({ - en: '', - fr: '', - de: '', - }); + t.Hint = { + En: '', + Fr: '', + De: '', + }; } - const hint = JSON.parse(t.Hint); return { ...text, Title: t.Title, - Hint: hint.en, - HintFr: hint.fr, - HintDe: hint.de, + Hint: t.Hint, ChoicesMap: choicesToChoicesMap(t.Choices), Type: TEXT, }; @@ -37,19 +34,16 @@ const unmarshalText = (text: any): types.TextQuestion => { const unmarshalRank = (rank: any): types.RankQuestion => { const r = rank as types.RankQuestion; if (r.Hint === undefined) { - r.Hint = JSON.stringify({ - en: '', - fr: '', - de: '', - }); + r.Hint = { + En: '', + Fr: '', + De: '', + }; } - const hint = JSON.parse(r.Hint); return { ...rank, Title: r.Title, - Hint: hint.en, - HintFr: hint.fr, - HintDe: hint.de, + Hint: r.Hint, ChoicesMap: choicesToChoicesMap(r.Choices), Type: RANK, }; @@ -58,19 +52,16 @@ const unmarshalRank = (rank: any): types.RankQuestion => { const unmarshalSelect = (select: any): types.SelectQuestion => { const s = select as types.SelectQuestion; if (s.Hint === undefined) { - s.Hint = JSON.stringify({ - en: '', - fr: '', - de: '', - }); + s.Hint = { + En: '', + Fr: '', + De: '', + }; } - const hint = JSON.parse(s.Hint); return { ...select, Title: s.Title, - Hint: hint.en, - HintFr: hint.fr, - HintDe: hint.de, + Hint: s.Hint, ChoicesMap: choicesToChoicesMap(s.Choices), Type: SELECT, }; diff --git a/web/frontend/src/types/configuration.ts b/web/frontend/src/types/configuration.ts index 3fce041ed..67e888c3e 100644 --- a/web/frontend/src/types/configuration.ts +++ b/web/frontend/src/types/configuration.ts @@ -12,6 +12,13 @@ interface Title { De: string; } +// Hint +interface Hint { + En: string; + Fr: string; + De: string; +} + interface SubjectElement { ID: ID; Type: string; @@ -24,9 +31,7 @@ interface RankQuestion extends SubjectElement { MinN: number; Choices: string[]; ChoicesMap: Map<string, string[]>; - Hint: string; - HintFr: string; - HintDe: string; + Hint: Hint; } // Text describes a "text" question, which allows the user to enter free text. interface TextQuestion extends SubjectElement { @@ -36,9 +41,7 @@ interface TextQuestion extends SubjectElement { Regex: string; Choices: string[]; ChoicesMap: Map<string, string[]>; - Hint: string; - HintFr: string; - HintDe: string; + Hint: Hint; } // Select describes a "select" question, which requires the user to select one @@ -48,9 +51,7 @@ interface SelectQuestion extends SubjectElement { MinN: number; Choices: string[]; ChoicesMap: Map<string, string[]>; - Hint: string; - HintFr: string; - HintDe: string; + Hint: Hint; } interface Subject extends SubjectElement { @@ -76,6 +77,7 @@ interface Answers { export type { ID, Title, + Hint, TextQuestion, SelectQuestion, RankQuestion, diff --git a/web/frontend/src/types/getObjectType.ts b/web/frontend/src/types/getObjectType.ts index c72739b17..b5e047d44 100644 --- a/web/frontend/src/types/getObjectType.ts +++ b/web/frontend/src/types/getObjectType.ts @@ -42,9 +42,11 @@ const newRank = (): types.RankQuestion => { Choices: [], ChoicesMap: new Map(Object.entries(obj)), Type: RANK, - Hint: '', - HintFr: '', - HintDe: '', + Hint: { + En: '', + Fr: '', + De: '', + }, }; }; @@ -61,9 +63,11 @@ const newSelect = (): types.SelectQuestion => { Choices: [], ChoicesMap: new Map(Object.entries(obj)), Type: SELECT, - Hint: '', - HintFr: '', - HintDe: '', + Hint: { + En: '', + Fr: '', + De: '', + }, }; }; @@ -82,9 +86,11 @@ const newText = (): types.TextQuestion => { Choices: [], ChoicesMap: new Map(Object.entries(obj)), Type: TEXT, - Hint: '', - HintFr: '', - HintDe: '', + Hint: { + En: '', + Fr: '', + De: '', + }, }; }; @@ -156,48 +162,30 @@ const toArraysOfSubjectElement = ( elements.forEach((element) => { switch (element.Type) { case RANK: - hint = JSON.stringify({ - en: (element as types.RankQuestion).Hint, - fr: (element as types.RankQuestion).HintFr, - de: (element as types.RankQuestion).HintDe, - }); rankQuestion.push({ ...(element as types.RankQuestion), Title: element.Title, Choices: choicesMapToChoices( (element as types.RankQuestion).ChoicesMap ), - Hint: hint, }); break; case SELECT: - hint = JSON.stringify({ - en: (element as types.SelectQuestion).Hint, - fr: (element as types.SelectQuestion).HintFr, - de: (element as types.SelectQuestion).HintDe, - }); selectQuestion.push({ ...(element as types.SelectQuestion), Title: element.Title, Choices: choicesMapToChoices( (element as types.SelectQuestion).ChoicesMap ), - Hint: hint, }); break; case TEXT: - hint = JSON.stringify({ - en: (element as types.TextQuestion).Hint, - fr: (element as types.TextQuestion).HintFr, - de: (element as types.TextQuestion).HintDe, - }); textQuestion.push({ ...(element as types.TextQuestion), Title: element.Title, Choices: choicesMapToChoices( (element as types.TextQuestion).ChoicesMap ), - Hint: hint, }); break; case SUBJECT: From 554498101571dbbb24ac69d1e3fd6c08ffc9dbb1 Mon Sep 17 00:00:00 2001 From: Carine Dengler <git@carine-dengler.de> Date: Tue, 21 Nov 2023 15:59:14 +0100 Subject: [PATCH 03/11] fix: remove internal ChoicesMap from exported JSON --- web/frontend/src/types/JSONparser.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/frontend/src/types/JSONparser.ts b/web/frontend/src/types/JSONparser.ts index 2b9ba1ea9..c293ad029 100644 --- a/web/frontend/src/types/JSONparser.ts +++ b/web/frontend/src/types/JSONparser.ts @@ -181,18 +181,21 @@ const unmarshalConfigAndCreateAnswers = ( const marshalText = (text: types.TextQuestion): any => { const newText: any = { ...text }; delete newText.Type; + delete newText.ChoicesMap; return newText; }; const marshalRank = (rank: types.RankQuestion): any => { const newRank: any = { ...rank }; delete newRank.Type; + delete newRank.ChoicesMap; return newRank; }; const marshalSelect = (select: types.SelectQuestion): any => { const newSelect: any = { ...select }; delete newSelect.Type; + delete newSelect.ChoicesMap; return newSelect; }; From 7f4cd8fadfbb0a8efc746116f12c73ae1cff397d Mon Sep 17 00:00:00 2001 From: Carine Dengler <git@carine-dengler.de> Date: Tue, 21 Nov 2023 17:15:19 +0100 Subject: [PATCH 04/11] fix: lint mock data --- web/frontend/src/mocks/mockData.ts | 129 +++++++++++++++++++---------- 1 file changed, 86 insertions(+), 43 deletions(-) diff --git a/web/frontend/src/mocks/mockData.ts b/web/frontend/src/mocks/mockData.ts index 5a2a6c5e8..44601c3bb 100644 --- a/web/frontend/src/mocks/mockData.ts +++ b/web/frontend/src/mocks/mockData.ts @@ -23,25 +23,30 @@ const mockRoster: string[] = [ ]; const mockForm1: any = { - Title: - { "En" : "Life on the campus", "Fr" : "Vie sur le campus", "De" : "Life on the campus"}, + Title: { En: 'Life on the campus', Fr: 'Vie sur le campus', De: 'Life on the campus' }, Scaffold: [ { ID: (0xa2ab).toString(), - Title: { "En" : "Rate the course", "Fr" : "Note la course", "De" : "Rate the course"}, + Title: { En: 'Rate the course', Fr: 'Note la course', De: 'Rate the course' }, Order: [(0x3fb2).toString(), (0x41e2).toString(), (0xcd13).toString(), (0xff31).toString()], Subjects: [ { - Title: - { "En" : "Let s talk about the food", "Fr" : "Parlons de la nourriture", "De" : "Let s talk about food"}, + Title: { + En: 'Let s talk about the food', + Fr: 'Parlons de la nourriture', + De: 'Let s talk about food', + }, ID: (0xff31).toString(), Order: [(0xa319).toString(), (0x19c7).toString()], Subjects: [], Texts: [], Selects: [ { - Title: - { "En" : "Select your ingredients", "Fr" : "Choisi tes ingrédients", "De" : "Select your ingredients"}, + Title: { + En: 'Select your ingredients', + Fr: 'Choisi tes ingrédients', + De: 'Select your ingredients', + }, ID: (0xa319).toString(), MaxN: 2, MinN: 1, @@ -50,13 +55,16 @@ const mockForm1: any = { '{"en": "salad", "fr": "salade", "de": "salad"}', '{"en": "onion", "fr": "oignon", "de": "onion"}', ], - Hint: {"En": "", "Fr": "", "De": ""}, + Hint: { En: '', Fr: '', De: '' }, }, ], Ranks: [ { - Title: - { "En" : "Rank the cafeteria", "Fr" : "Ordonne les cafet", "De" : "Rank the cafeteria"}, + Title: { + En: 'Rank the cafeteria', + Fr: 'Ordonne les cafet', + De: 'Rank the cafeteria', + }, ID: (0x19c7).toString(), MaxN: 3, MinN: 3, @@ -65,7 +73,7 @@ const mockForm1: any = { '{"en": "SV", "fr": "SV", "de": "SV"}', '{"en": "Parmentier", "fr": "Parmentier", "de": "Parmentier"}', ], - Hint: {"En": "", "Fr": "", "De": ""}, + Hint: { En: '', Fr: '', De: '' }, }, ], }, @@ -73,8 +81,11 @@ const mockForm1: any = { Ranks: [], Selects: [ { - Title: - {"En" : "How did you find the provided material, from 1 (bad) to 5 (excellent) ?", "Fr" : "Comment trouves-tu le matériel fourni, de 1 (mauvais) à 5 (excellent) ?", "De" : "How did you find the provided material, from 1 (bad) to 5 (excellent) ?"}, + Title: { + En: 'How did you find the provided material, from 1 (bad) to 5 (excellent) ?', + Fr: 'Comment trouves-tu le matériel fourni, de 1 (mauvais) à 5 (excellent) ?', + De: 'How did you find the provided material, from 1 (bad) to 5 (excellent) ?', + }, ID: (0x3fb2).toString(), MaxN: 1, MinN: 1, @@ -85,11 +96,14 @@ const mockForm1: any = { '{"en":"4", "fr": "4", "de": "4"}', '{ "en": "5", "fr": "5", "de": "5" }', ], - Hint: {"En": "", "Fr": "", "De": ""}, + Hint: { En: '', Fr: '', De: '' }, }, { - Title: - {"En": "How did you find the teaching ?","Fr": "Comment trouves-tu l enseignement ?","De": "How did you find the teaching ?"}, + Title: { + En: 'How did you find the teaching ?', + Fr: 'Comment trouves-tu l enseignement ?', + De: 'How did you find the teaching ?', + }, ID: (0x41e2).toString(), MaxN: 1, MinN: 1, @@ -98,13 +112,20 @@ const mockForm1: any = { '{"en" : "normal", "fr": "normal", "de": "normal"}', '{"en" : "good", "fr": "super", "de": "good"}', ], - Hint: {"En": "Be honest. This is anonymous anyway", "Fr": "Sois honnête. C est anonyme de toute façon", "De": "Be honest. This is anonymous anyway"}, + Hint: { + En: 'Be honest. This is anonymous anyway', + Fr: 'Sois honnête. C est anonyme de toute façon', + De: 'Be honest. This is anonymous anyway', + }, }, ], Texts: [ { - Title: - { "En" : "Who were the two best TAs ?", "Fr" : "Quels sont les deux meilleurs TA ?", "De" : "Who were the two best TAs ?"} , + Title: { + En: 'Who were the two best TAs ?', + Fr: 'Quels sont les deux meilleurs TA ?', + De: 'Who were the two best TAs ?', + }, ID: (0xcd13).toString(), MaxLength: 20, MaxN: 2, @@ -114,7 +135,7 @@ const mockForm1: any = { '{"en":"TA1", "fr": "TA1", "de": "TA1"}', '{"en":"TA2", "fr":"TA2","de": "TA2"}', ], - Hint: {"En": "", "Fr": "", "De": ""}, + Hint: { En: '', Fr: '', De: '' }, }, ], }, @@ -148,18 +169,20 @@ const mockFormResult12: Results = { }; const mockForm2: any = { - Title: - {"En": "Please give your opinion", "Fr": "Donne ton avis", "De": "Please give your opinion"}, + Title: { En: 'Please give your opinion', Fr: 'Donne ton avis', De: 'Please give your opinion' }, Scaffold: [ { ID: (0xa2ab).toString(), - Title: {"En": "Rate the course", "Fr": "Note le cours", "De": "Rate the course"}, + Title: { En: 'Rate the course', Fr: 'Note le cours', De: 'Rate the course' }, Order: [(0x3fb2).toString(), (0xcd13).toString()], Selects: [ { - Title: - {"En": "How did you find the provided material, from 1 (bad) to 5 (excellent) ?", "Fr" : "Comment trouves-tu le matériel fourni, de 1 (mauvais) à 5 (excellent) ?", "De" : "How did you find the provided material, from 1 (bad) to 5 (excellent) ?"}, + Title: { + En: 'How did you find the provided material, from 1 (bad) to 5 (excellent) ?', + Fr: 'Comment trouves-tu le matériel fourni, de 1 (mauvais) à 5 (excellent) ?', + De: 'How did you find the provided material, from 1 (bad) to 5 (excellent) ?', + }, ID: (0x3fb2).toString(), MaxN: 1, MinN: 1, @@ -170,13 +193,16 @@ const mockForm2: any = { '{"en":"4", "fr": "4", "de": "4"}', '{ "en": "5", "fr": "5", "de": "5" }', ], - Hint: {"En": "", "Fr": "", "De": ""}, + Hint: { En: '', Fr: '', De: '' }, }, ], Texts: [ { - Title: - {"En" : "Who were the two best TAs ?", "Fr" : "Quels sont les deux meilleurs TA ?", "De" : "Who were the two best TAs ?"}, + Title: { + En: 'Who were the two best TAs ?', + Fr: 'Quels sont les deux meilleurs TA ?', + De: 'Who were the two best TAs ?', + }, ID: (0xcd13).toString(), MaxLength: 40, MaxN: 2, @@ -186,7 +212,7 @@ const mockForm2: any = { '{"en":"TA2", "fr":"TA2","de": "TA2"}', ], Regex: '^[A-Z][a-z]+$', - Hint: {"En": "", "Fr": "", "De": ""}, + Hint: { En: '', Fr: '', De: '' }, }, ], @@ -195,12 +221,15 @@ const mockForm2: any = { }, { ID: (0x1234).toString(), - Title: {"En": "Tough choices", "Fr": "Choix difficiles", "De": "Tough choices"}, + Title: { En: 'Tough choices', Fr: 'Choix difficiles', De: 'Tough choices' }, Order: [(0xa319).toString(), (0xcafe).toString(), (0xbeef).toString()], Selects: [ { - Title: - {"En": "Select your ingredients", "Fr": "Choisis tes ingrédients", "De": "Select your ingredients"}, + Title: { + En: 'Select your ingredients', + Fr: 'Choisis tes ingrédients', + De: 'Select your ingredients', + }, ID: (0xa319).toString(), MaxN: 3, MinN: 0, @@ -210,14 +239,17 @@ const mockForm2: any = { '{"en": "onion", "fr": "oignon", "de": "onion"}', '{"en": "falafel", "fr": "falafel", "de": "falafel"}', ], - Hint: {"En": "", "Fr": "", "De": ""}, + Hint: { En: '', Fr: '', De: '' }, }, ], Ranks: [ { - Title: - {"En": "Which cafeteria serves the best coffee ?", "Fr": "Quelle cafétéria sert le meilleur café ?", "De": "Which cafeteria serves the best coffee ?"}, + Title: { + En: 'Which cafeteria serves the best coffee ?', + Fr: 'Quelle cafétéria sert le meilleur café ?', + De: 'Which cafeteria serves the best coffee ?', + }, ID: (0xcafe).toString(), MaxN: 4, MinN: 1, @@ -227,15 +259,19 @@ const mockForm2: any = { '{"en": "Arcadie", "fr": "Arcadie", "de": "Arcadie"}', '{"en": "Montreux Jazz Cafe", "fr": "Montreux Jazz Cafe", "de": "Montreux Jazz Cafe"}', ], - Hint: {"En": "", "Fr": "", "De": ""}, + Hint: { En: '', Fr: '', De: '' }, }, { - Title: {"En": "IN or SC ?", "Fr": "IN ou SC ?", "De": "IN or SC ?"}, + Title: { En: 'IN or SC ?', Fr: 'IN ou SC ?', De: 'IN or SC ?' }, ID: (0xbeef).toString(), MaxN: 2, MinN: 1, Choices: ['{"en": "IN", "fr": "IN", "de": "IN"}', '{"en": "SC", "fr": "SC", "de": "SC"}'], - Hint: {"En": "The right answer is IN ;-)", "Fr": "La bonne réponse est IN ;-)", "De": "The right answer is IN ;-)"}, + Hint: { + En: 'The right answer is IN ;-)', + Fr: 'La bonne réponse est IN ;-)', + De: 'The right answer is IN ;-)', + }, }, ], Texts: [], @@ -245,19 +281,22 @@ const mockForm2: any = { }; const mockForm3: any = { - Title: {"En": "Lunch", "Fr": "Déjeuner", "De": "Lunch"}, + Title: { En: 'Lunch', Fr: 'Déjeuner', De: 'Lunch' }, Scaffold: [ { ID: '3cVHIxpx', - Title: {"En": "Choose your lunch", "Fr": "Choisis ton déjeuner", "De": "Choose your lunch"}, + Title: { En: 'Choose your lunch', Fr: 'Choisis ton déjeuner', De: 'Choose your lunch' }, Order: ['PGP'], Ranks: [], Selects: [], Texts: [ { ID: 'PGP', - Title: - {"En": "Select what you want", "Fr": "Choisis ce que tu veux", "De": "Select what you want"}, + Title: { + En: 'Select what you want', + Fr: 'Choisis ce que tu veux', + De: 'Select what you want', + }, MaxN: 4, MinN: 0, MaxLength: 50, @@ -268,7 +307,11 @@ const mockForm3: any = { '{"en": "Drink 🧃", "fr": "Boisson 🧃", "de": "Drink 🧃"}', '{"en":"Dessert 🍰", "fr": "Dessert 🍰", "de": "Dessert 🍰"}', ], - Hint: {"En": "If you change opinion call me before 11:30 a.m.", "Fr": "Si tu changes d\avis appelle moi avant 11h30", "De": "If you change opinion call me before 11:30 a.m."}, + Hint: { + En: 'If you change opinion call me before 11:30 a.m.', + Fr: "Si tu changes d'avis appelle moi avant 11h30", + De: 'If you change opinion call me before 11:30 a.m.', + }, }, ], Subjects: [], From a197a79777a7dfa51206f48ae76feb61794d825b Mon Sep 17 00:00:00 2001 From: Carine Dengler <git@carine-dengler.de> Date: Tue, 21 Nov 2023 17:20:11 +0100 Subject: [PATCH 05/11] fix: run prettier --- web/frontend/src/schema/form_conf.json | 48 +++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/web/frontend/src/schema/form_conf.json b/web/frontend/src/schema/form_conf.json index be2cff897..a18507537 100644 --- a/web/frontend/src/schema/form_conf.json +++ b/web/frontend/src/schema/form_conf.json @@ -5,9 +5,9 @@ "Title": { "type": "object", "properties": { - "En": { "type": "string" }, - "Fr": { "type": "string" }, - "De": { "type": "string" } + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } } }, "Scaffold": { @@ -19,9 +19,9 @@ "Title": { "type": "object", "properties": { - "En": { "type": "string" }, - "Fr": { "type": "string" }, - "De": { "type": "string" } + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } } }, "Order": { @@ -41,9 +41,9 @@ "Title": { "type": "object", "properties": { - "En": { "type": "string" }, - "Fr": { "type": "string" }, - "De": { "type": "string" } + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } } }, "MaxN": { "type": "number" }, @@ -55,9 +55,9 @@ "Hint": { "type": "object", "properties": { - "En": { "type": "string" }, - "Fr": { "type": "string" }, - "De": { "type": "string" } + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } } } }, @@ -74,9 +74,9 @@ "Title": { "type": "object", "properties": { - "En": { "type": "string" }, - "Fr": { "type": "string" }, - "De": { "type": "string" } + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } } }, "MaxN": { "type": "number" }, @@ -88,9 +88,9 @@ "Hint": { "type": "object", "properties": { - "En": { "type": "string" }, - "Fr": { "type": "string" }, - "De": { "type": "string" } + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } } } }, @@ -107,9 +107,9 @@ "Title": { "type": "object", "properties": { - "En": { "type": "string" }, - "Fr": { "type": "string" }, - "De": { "type": "string" } + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } } }, "MaxN": { "type": "number" }, @@ -123,9 +123,9 @@ "Hint": { "type": "object", "properties": { - "En": { "type": "string" }, - "Fr": { "type": "string" }, - "De": { "type": "string" } + "En": { "type": "string" }, + "Fr": { "type": "string" }, + "De": { "type": "string" } } } }, From c04242d6be6d140de0e65e925c038c7f1c694bed Mon Sep 17 00:00:00 2001 From: Carine Dengler <git@carine-dengler.de> Date: Tue, 21 Nov 2023 17:25:46 +0100 Subject: [PATCH 06/11] fix: fix Go tests --- contracts/evoting/types/ballots_test.go | 38 ++++++++++++------------- integration/performance_test.go | 6 ++-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/contracts/evoting/types/ballots_test.go b/contracts/evoting/types/ballots_test.go index 1a94e6aac..bc94dea27 100644 --- a/contracts/evoting/types/ballots_test.go +++ b/contracts/evoting/types/ballots_test.go @@ -56,13 +56,13 @@ func TestBallot_Unmarshal(t *testing.T) { Selects: []Select{{ ID: decodedQuestionID(1), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 2, MinN: 2, Choices: make([]string, 3), }, { ID: decodedQuestionID(2), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 3, MinN: 3, Choices: make([]string, 5), @@ -70,7 +70,7 @@ func TestBallot_Unmarshal(t *testing.T) { Ranks: []Rank{{ ID: decodedQuestionID(3), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 4, MinN: 0, Choices: make([]string, 4), @@ -78,7 +78,7 @@ func TestBallot_Unmarshal(t *testing.T) { Texts: []Text{{ ID: decodedQuestionID(4), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 2, MinN: 2, MaxLength: 10, @@ -305,7 +305,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { subject := Subject{ Subjects: []Subject{{ ID: "", - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, Order: nil, Subjects: []Subject{}, Selects: []Select{}, @@ -315,13 +315,13 @@ func TestSubject_MaxEncodedSize(t *testing.T) { Selects: []Select{{ ID: encodedQuestionID(1), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 3, MinN: 0, Choices: make([]string, 3), }, { ID: encodedQuestionID(2), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 5, MinN: 0, Choices: make([]string, 5), @@ -329,7 +329,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { Ranks: []Rank{{ ID: encodedQuestionID(3), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 4, MinN: 0, Choices: make([]string, 4), @@ -337,7 +337,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { Texts: []Text{{ ID: encodedQuestionID(4), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 2, MinN: 0, MaxLength: 10, @@ -345,7 +345,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { Choices: make([]string, 2), }, { ID: encodedQuestionID(5), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 1, MinN: 0, MaxLength: 10, @@ -355,7 +355,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { } conf := Configuration{ - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, Scaffold: []Subject{subject}, } @@ -368,7 +368,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { func TestSubject_IsValid(t *testing.T) { mainSubject := &Subject{ ID: ID(base64.StdEncoding.EncodeToString([]byte("S1"))), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, Order: []ID{}, Subjects: []Subject{}, Selects: []Select{}, @@ -378,7 +378,7 @@ func TestSubject_IsValid(t *testing.T) { subSubject := &Subject{ ID: ID(base64.StdEncoding.EncodeToString([]byte("S2"))), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, Order: []ID{}, Subjects: []Subject{}, Selects: []Select{}, @@ -387,7 +387,7 @@ func TestSubject_IsValid(t *testing.T) { } configuration := Configuration{ - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, Scaffold: []Subject{*mainSubject, *subSubject}, } @@ -400,7 +400,7 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Selects = []Select{{ ID: encodedQuestionID(1), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 0, MinN: 0, Choices: make([]string, 0), @@ -408,7 +408,7 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Ranks = []Rank{{ ID: encodedQuestionID(1), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 0, MinN: 0, Choices: make([]string, 0), @@ -423,7 +423,7 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Ranks[0] = Rank{ ID: encodedQuestionID(2), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 0, MinN: 2, Choices: make([]string, 0), @@ -439,7 +439,7 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Ranks = []Rank{} mainSubject.Selects[0] = Select{ ID: encodedQuestionID(1), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 1, MinN: 0, Choices: make([]string, 0), @@ -455,7 +455,7 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Selects = []Select{} mainSubject.Texts = []Text{{ ID: encodedQuestionID(3), - Title: {En: "", Fr: "", De: ""}, + Title: Title{En: "", Fr: "", De: ""}, MaxN: 2, MinN: 4, MaxLength: 0, diff --git a/integration/performance_test.go b/integration/performance_test.go index aab340679..71aca31b2 100644 --- a/integration/performance_test.go +++ b/integration/performance_test.go @@ -61,7 +61,7 @@ func BenchmarkIntegration_CustomVotesScenario(b *testing.B) { } // ##### CREATE FORM ##### - formID, err := createFormNChunks(m, "Three votes form", adminID, numChunksPerBallot) + formID, err := createFormNChunks(m, types.Title{En: "Three votes form", Fr: "", De: ""}, adminID, numChunksPerBallot) require.NoError(b, err) time.Sleep(time.Millisecond * 1000) @@ -157,14 +157,14 @@ func createFormNChunks(m txManager, title types.Title, admin string, numChunks i Scaffold: []types.Subject{ { ID: "aa", - Title: {En: "subject1", Fr: "", De: ""}, + Title: types.Title{En: "subject1", Fr: "", De: ""}, Order: nil, Subjects: nil, Selects: nil, Ranks: []types.Rank{}, Texts: []types.Text{{ ID: "bb", - Title: {En: "Enter favorite snack", Fr: "", De: ""}, + Title: types.Title{En: "Enter favorite snack", Fr: "", De: ""}, MaxN: 1, MinN: 0, MaxLength: uint(base64.StdEncoding.DecodedLen(textSize)), From 79f72a41098783e4ff284a6278d6efd55fe4248f Mon Sep 17 00:00:00 2001 From: Carine Dengler <git@carine-dengler.de> Date: Wed, 22 Nov 2023 17:20:18 +0100 Subject: [PATCH 07/11] refactor: use internationalize function --- .../src/pages/ballot/components/BallotDisplay.tsx | 9 +++------ web/frontend/src/pages/ballot/components/Rank.tsx | 9 +++------ web/frontend/src/pages/ballot/components/Select.tsx | 9 +++------ web/frontend/src/pages/ballot/components/Text.tsx | 9 +++------ web/frontend/src/pages/form/GroupedResult.tsx | 11 +++-------- web/frontend/src/pages/form/IndividualResult.tsx | 9 +++------ web/frontend/src/pages/form/Result.tsx | 5 ++--- web/frontend/src/pages/form/Show.tsx | 5 ++--- web/frontend/src/pages/form/components/FormForm.tsx | 5 ++--- web/frontend/src/pages/form/components/Question.tsx | 7 ++----- .../src/pages/form/components/SubjectComponent.tsx | 5 ++--- web/frontend/src/pages/utils.ts | 12 ++++++++++++ 12 files changed, 40 insertions(+), 55 deletions(-) create mode 100644 web/frontend/src/pages/utils.ts diff --git a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx index f4d9ff732..50769a30b 100644 --- a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx +++ b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx @@ -5,6 +5,7 @@ import Rank, { handleOnDragEnd } from './Rank'; import Select from './Select'; import Text from './Text'; import { DragDropContext } from 'react-beautiful-dnd'; +import { internationalize } from './../../utils'; type BallotDisplayProps = { configuration: Configuration; @@ -58,9 +59,7 @@ const BallotDisplay: FC<BallotDisplayProps> = ({ return ( <div key={subject.ID}> <h3 className="text-xl break-all pt-1 pb-1 sm:pt-2 sm:pb-2 border-t font-bold text-gray-600"> - {language === 'en' && subject.Title.En} - {language === 'fr' && subject.Title.Fr} - {language === 'de' && subject.Title.De} + {internationalize(language, subject.Title)} </h3> {subject.Order.map((id: ID) => ( <div key={id}> @@ -81,9 +80,7 @@ const BallotDisplay: FC<BallotDisplayProps> = ({ <DragDropContext onDragEnd={(dropRes) => handleOnDragEnd(dropRes, answers, setAnswers)}> <div className="w-full mb-0 sm:mb-4 mt-4 sm:mt-6"> <h3 className="pb-6 break-all text-2xl text-center text-gray-700"> - {language === 'en' && titles.en} - {language === 'fr' && titles.fr} - {language === 'de' && titles.de} + {internationalize(language, titles)} </h3> <div className="flex flex-col"> {configuration.Scaffold.map((subject: types.Subject) => SubjectTree(subject))} diff --git a/web/frontend/src/pages/ballot/components/Rank.tsx b/web/frontend/src/pages/ballot/components/Rank.tsx index 437671384..9bf4f9016 100644 --- a/web/frontend/src/pages/ballot/components/Rank.tsx +++ b/web/frontend/src/pages/ballot/components/Rank.tsx @@ -3,6 +3,7 @@ import { Draggable, DropResult, Droppable } from 'react-beautiful-dnd'; import { Answers, ID, RankQuestion } from 'types/configuration'; import { answersFrom } from 'types/getObjectType'; import HintButton from 'components/buttons/HintButton'; +import { internationalize } from './../../utils'; export const handleOnDragEnd = ( result: DropResult, @@ -87,15 +88,11 @@ const Rank: FC<RankProps> = ({ rank, answers, language }) => { <div className="grid grid-rows-1 grid-flow-col"> <div> <h3 className="text-lg break-words text-gray-600"> - {language === 'en' && titles.en} - {language === 'fr' && titles.fr} - {language === 'de' && titles.de} + {internationalize(language, titles)} </h3> </div> <div className="text-right"> - {language === 'en' && <HintButton text={rank.Hint.En} />} - {language === 'fr' && <HintButton text={rank.Hint.Fr} />} - {language === 'de' && <HintButton text={rank.Hint.De} />} + {<HintButton text={internationalize(language, rank.Hint)} />} </div> </div> <div className="mt-5 px-4 max-w-[300px] sm:pl-8 sm:max-w-md"> diff --git a/web/frontend/src/pages/ballot/components/Select.tsx b/web/frontend/src/pages/ballot/components/Select.tsx index f93dfead9..8b3c2c7a6 100644 --- a/web/frontend/src/pages/ballot/components/Select.tsx +++ b/web/frontend/src/pages/ballot/components/Select.tsx @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'; import { Answers, SelectQuestion } from 'types/configuration'; import { answersFrom } from 'types/getObjectType'; import HintButton from 'components/buttons/HintButton'; +import { internationalize } from './../../utils'; type SelectProps = { select: SelectQuestion; answers: Answers; @@ -81,15 +82,11 @@ const Select: FC<SelectProps> = ({ select, answers, setAnswers, language }) => { <div className="grid grid-rows-1 grid-flow-col"> <div> <h3 className="text-lg break-words text-gray-600"> - {language === 'en' && titles.en} - {language === 'fr' && titles.fr} - {language === 'de' && titles.de} + {internationalize(language, titles)} </h3> </div> <div className="text-right"> - {language === 'en' && <HintButton text={select.Hint.En} />} - {language === 'fr' && <HintButton text={select.Hint.Fr} />} - {language === 'de' && <HintButton text={select.Hint.De} />} + {<HintButton text={internationalize(language, select.Hint)} />} </div> </div> <div className="pt-1">{requirementsDisplay()}</div> diff --git a/web/frontend/src/pages/ballot/components/Text.tsx b/web/frontend/src/pages/ballot/components/Text.tsx index d065723f5..3358d0784 100644 --- a/web/frontend/src/pages/ballot/components/Text.tsx +++ b/web/frontend/src/pages/ballot/components/Text.tsx @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'; import { Answers, TextQuestion } from 'types/configuration'; import { answersFrom } from 'types/getObjectType'; import HintButton from 'components/buttons/HintButton'; +import { internationalize } from './../../utils'; type TextProps = { text: TextQuestion; @@ -101,15 +102,11 @@ const Text: FC<TextProps> = ({ text, answers, setAnswers, language }) => { <div className="grid grid-rows-1 grid-flow-col"> <div> <h3 className="text-lg break-words text-gray-600 w-96"> - {language === 'en' && text.Title.En} - {language === 'fr' && text.Title.Fr} - {language === 'de' && text.Title.De} + {internationalize(language, text.Title)} </h3> </div> <div className="text-right"> - {language === 'en' && <HintButton text={text.Hint.En} />} - {language === 'fr' && <HintButton text={text.Hint.Fr} />} - {language === 'de' && <HintButton text={text.Hint.De} />} + {<HintButton text={internationalize(language, text.Hint)} />} </div> </div> <div className="pt-1">{requirementsDisplay()}</div> diff --git a/web/frontend/src/pages/form/GroupedResult.tsx b/web/frontend/src/pages/form/GroupedResult.tsx index e97103baf..bb077bfd4 100644 --- a/web/frontend/src/pages/form/GroupedResult.tsx +++ b/web/frontend/src/pages/form/GroupedResult.tsx @@ -29,6 +29,7 @@ import { import { default as i18n } from 'i18next'; import SelectResult from './components/SelectResult'; import TextResult from './components/TextResult'; +import { internationalize } from './../utils'; type GroupedResultProps = { rankResult: RankResults; @@ -59,11 +60,7 @@ const GroupedResult: FC<GroupedResultProps> = ({ rankResult, selectResult, textR <div className="align-text-middle flex mt-1 mr-2 h-5 w-5" aria-hidden="true"> {questionIcons[element.Type]} </div> - <h2 className="text-lg pb-2"> - {i18n.language === 'en' && titles.En} - {i18n.language === 'fr' && titles.Fr} - {i18n.language === 'de' && titles.De} - </h2> + <h2 className="text-lg pb-2">{internationalize(i18n.language, titles)}</h2> </div> {element.Type === RANK && rankResult.has(element.ID) && ( <RankResult rank={element as RankQuestion} rankResult={rankResult.get(element.ID)} /> @@ -85,9 +82,7 @@ const GroupedResult: FC<GroupedResultProps> = ({ rankResult, selectResult, textR return ( <div key={subject.ID}> <h2 className="text-xl pt-1 pb-1 sm:pt-2 sm:pb-2 border-t font-bold text-gray-600"> - {i18n.language === 'en' && subject.Title.En} - {i18n.language === 'fr' && subject.Title.Fr} - {i18n.language === 'de' && subject.Title.De} + {internationalize(i18n.language, subject.Title)} </h2> {subject.Order.map((id: ID) => ( <div key={id}> diff --git a/web/frontend/src/pages/form/IndividualResult.tsx b/web/frontend/src/pages/form/IndividualResult.tsx index 14bab6a33..8903a4255 100644 --- a/web/frontend/src/pages/form/IndividualResult.tsx +++ b/web/frontend/src/pages/form/IndividualResult.tsx @@ -9,6 +9,7 @@ import { import { IndividualSelectResult } from './components/SelectResult'; import { IndividualTextResult } from './components/TextResult'; import { IndividualRankResult } from './components/RankResult'; +import { internationalize } from './../utils'; import { useTranslation } from 'react-i18next'; import { ID, @@ -76,9 +77,7 @@ const IndividualResult: FC<IndividualResultProps> = ({ {questionIcons[element.Type]} </div> <h2 className="flex align-text-middle text-lg pb-2"> - {i18n.language === 'en' && element.Title.En} - {i18n.language === 'fr' && element.Title.Fr} - {i18n.language === 'de' && element.Title.De} + {internationalize(i18n.language, element.Title)} </h2> </div> {element.Type === RANK && rankResult.has(element.ID) && ( @@ -110,9 +109,7 @@ const IndividualResult: FC<IndividualResultProps> = ({ return ( <div key={subject.ID}> <h2 className="text-xl pt-1 pb-1 sm:pt-2 sm:pb-2 border-t font-bold text-gray-600"> - {i18n.language === 'en' && subject.Title.En} - {i18n.language === 'fr' && subject.Title.Fr} - {i18n.language === 'de' && subject.Title.De} + {internationalize(i18n.language, subject.Title)} </h2> {subject.Order.map((id: ID) => ( <div key={id}> diff --git a/web/frontend/src/pages/form/Result.tsx b/web/frontend/src/pages/form/Result.tsx index 582bd66d5..94d714e8c 100644 --- a/web/frontend/src/pages/form/Result.tsx +++ b/web/frontend/src/pages/form/Result.tsx @@ -12,6 +12,7 @@ import { Tab } from '@headlessui/react'; import IndividualResult from './IndividualResult'; import { default as i18n } from 'i18next'; import GroupedResult from './GroupedResult'; +import { internationalize } from './../utils'; // Functional component that displays the result of the votes const FormResult: FC = () => { @@ -97,9 +98,7 @@ const FormResult: FC = () => { {t('totalNumberOfVotes', { votes: result.length })} </h2> <h3 className="py-6 border-t text-2xl text-center text-gray-700"> - {i18n.language === 'en' && configuration.Title.En} - {i18n.language === 'fr' && configuration.Title.Fr} - {i18n.language === 'de' && configuration.Title.De} + {internationalize(i18n.language, configuration.Title)} </h3> <div> diff --git a/web/frontend/src/pages/form/Show.tsx b/web/frontend/src/pages/form/Show.tsx index 8ca7aa8ab..f11355635 100644 --- a/web/frontend/src/pages/form/Show.tsx +++ b/web/frontend/src/pages/form/Show.tsx @@ -15,6 +15,7 @@ import useGetResults from './components/utils/useGetResults'; import UserIDTable from './components/UserIDTable'; import DKGStatusTable from './components/DKGStatusTable'; import LoadingButton from './components/LoadingButton'; +import { internationalize } from './../utils'; import { default as i18n } from 'i18next'; const FormShow: FC = () => { @@ -222,9 +223,7 @@ const FormShow: FC = () => { {!loading ? ( <> <div className="pt-8 text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate"> - {i18n.language === 'en' && titles.en} - {i18n.language === 'fr' && titles.fr} - {i18n.language === 'de' && titles.de} + {internationalize(i18n.language, titles)} </div> <div className="pt-2 break-all">Form ID : {formId}</div> diff --git a/web/frontend/src/pages/form/components/FormForm.tsx b/web/frontend/src/pages/form/components/FormForm.tsx index a811b3961..ce2bb37a4 100644 --- a/web/frontend/src/pages/form/components/FormForm.tsx +++ b/web/frontend/src/pages/form/components/FormForm.tsx @@ -8,6 +8,7 @@ import { CloudUploadIcon, PencilIcon, TrashIcon } from '@heroicons/react/solid'; import SubjectComponent from './SubjectComponent'; import UploadFile from './UploadFile'; import pollTransaction from './utils/TransactionPoll'; +import { internationalize } from './../../utils'; import configurationSchema from '../../../schema/configurationValidation'; import { Configuration, ID, Subject } from '../../../types/configuration'; @@ -238,9 +239,7 @@ const FormForm: FC<FormFormProps> = () => { <div className="mt-1 ml-3 w-[90%] break-words" onClick={() => setTitleChanging(true)}> - {language === 'en' && Title.En} - {language === 'fr' && Title.Fr} - {language === 'de' && Title.De} + {internationalize(language, Title)} </div> <div className="ml-1"> <button diff --git a/web/frontend/src/pages/form/components/Question.tsx b/web/frontend/src/pages/form/components/Question.tsx index 539d36984..c49ba1d64 100644 --- a/web/frontend/src/pages/form/components/Question.tsx +++ b/web/frontend/src/pages/form/components/Question.tsx @@ -7,6 +7,7 @@ import { RankQuestion, SelectQuestion, TextQuestion } from 'types/configuration' import SubjectDropdown from './SubjectDropdown'; import AddQuestionModal from './AddQuestionModal'; import DisplayTypeIcon from './DisplayTypeIcon'; +import { internationalize } from './../../utils'; type QuestionProps = { question: RankQuestion | SelectQuestion | TextQuestion; notifyParent(question: RankQuestion | SelectQuestion | TextQuestion): void; @@ -52,11 +53,7 @@ const Question: FC<QuestionProps> = ({ question, notifyParent, removeQuestion, l <div className="h-9 w-9 rounded-full bg-gray-100 mr-2 ml-1"> <DisplayTypeIcon Type={Type} /> </div> - <div className="pt-1.5 max-w-md pr-8 truncate"> - {language === 'en' && (Title.En.length ? Title.En : `Enter ${Type} title`)} - {language === 'fr' && (Title.Fr.length ? Title.Fr : Title.En)} - {language === 'de' && (Title.De.length ? Title.De : Title.En)} - </div> + <div className="pt-1.5 max-w-md pr-8 truncate">{internationalize(language, Title)}</div> </div> <div className="flex mt-2 ml-2"> diff --git a/web/frontend/src/pages/form/components/SubjectComponent.tsx b/web/frontend/src/pages/form/components/SubjectComponent.tsx index 1658fbb06..7ae0f5355 100644 --- a/web/frontend/src/pages/form/components/SubjectComponent.tsx +++ b/web/frontend/src/pages/form/components/SubjectComponent.tsx @@ -20,6 +20,7 @@ import { PencilIcon } from '@heroicons/react/solid'; import AddQuestionModal from './AddQuestionModal'; import { useTranslation } from 'react-i18next'; import RemoveElementModal from './RemoveElementModal'; +import { internationalize } from './../../utils'; const MAX_NESTED_SUBJECT = 1; type SubjectComponentProps = { @@ -360,9 +361,7 @@ const SubjectComponent: FC<SubjectComponentProps> = ({ ) : ( <div className="flex mb-2 max-w-md truncate"> <div className="pt-1.5 truncate" onClick={() => setTitleChanging(true)}> - {language === 'en' && Title.En} - {language === 'fr' && Title.Fr} - {language === 'de' && Title.De} + {internationalize(language, Title)} </div> <div className="ml-1 pr-10"> <button diff --git a/web/frontend/src/pages/utils.ts b/web/frontend/src/pages/utils.ts new file mode 100644 index 000000000..faa5f3f9f --- /dev/null +++ b/web/frontend/src/pages/utils.ts @@ -0,0 +1,12 @@ +import { Hint, Title } from 'types/configuration'; + +export function internationalize(language: string, internationalizable: Hint | Title): string { + switch (language) { + case 'fr': + return internationalizable.Fr; + case 'de': + return internationalizable.De; + default: + return internationalizable.En; + } +} From c08ba7913d720256eadcaa5d8ca96b3c7e0431f4 Mon Sep 17 00:00:00 2001 From: Carine Dengler <git@carine-dengler.de> Date: Wed, 22 Nov 2023 17:30:27 +0100 Subject: [PATCH 08/11] refactor: remove debugging --- web/frontend/src/pages/form/components/FormForm.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/frontend/src/pages/form/components/FormForm.tsx b/web/frontend/src/pages/form/components/FormForm.tsx index ce2bb37a4..50665ce27 100644 --- a/web/frontend/src/pages/form/components/FormForm.tsx +++ b/web/frontend/src/pages/form/components/FormForm.tsx @@ -100,7 +100,6 @@ const FormForm: FC<FormFormProps> = () => { try { await configurationSchema.validate(data.Configuration); } catch (err: any) { - console.log(data.Configuration); setTextModal(t('errorIncorrectConfSchema') + err.errors.join(',')); setShowModal(true); return; @@ -147,7 +146,6 @@ const FormForm: FC<FormFormProps> = () => { try { await configurationSchema.validate(data); } catch (err: any) { - console.log(data); setTextModal(t('errorIncorrectConfSchema') + err.errors.join(',')); setShowModal(true); return; From e4285ced5a1a6ce955f0ea85001f5502d8c80cae Mon Sep 17 00:00:00 2001 From: Carine Dengler <git@carine-dengler.de> Date: Wed, 22 Nov 2023 17:30:51 +0100 Subject: [PATCH 09/11] refactor: remove dead code --- web/frontend/src/types/getObjectType.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/web/frontend/src/types/getObjectType.ts b/web/frontend/src/types/getObjectType.ts index b5e047d44..82ef1b156 100644 --- a/web/frontend/src/types/getObjectType.ts +++ b/web/frontend/src/types/getObjectType.ts @@ -158,7 +158,6 @@ const toArraysOfSubjectElement = ( const selectQuestion: types.SelectQuestion[] = []; const textQuestion: types.TextQuestion[] = []; const subjects: types.Subject[] = []; - let hint = ''; elements.forEach((element) => { switch (element.Type) { case RANK: From f632702672c8ff4cc5e1f4b825960e9e23820eef Mon Sep 17 00:00:00 2001 From: Carine Dengler <git@carine-dengler.de> Date: Thu, 23 Nov 2023 15:01:55 +0100 Subject: [PATCH 10/11] fix: updated local_forms script w/ current data format --- scripts/local_forms.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/local_forms.sh b/scripts/local_forms.sh index 3a8359169..1cb5bcdde 100755 --- a/scripts/local_forms.sh +++ b/scripts/local_forms.sh @@ -4,7 +4,7 @@ SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) . "$SCRIPT_DIR/local_login.sh" echo "add form" -RESP=$(curl -sk "$FRONTEND_URL/api/evoting/forms" -X POST -H 'Content-Type: application/json' -b cookies.txt --data-raw '{"Configuration":{"MainTitle":"{\"en\":\"Colours\",\"fr\":\"\",\"de\":\"\"}","Scaffold":[{"ID":"5DRhKsY2","Title":"Colours","TitleFr":"","TitleDe":"","Order":["d0mSUfpv"],"Ranks":[],"Selects":[{"ID":"d0mSUfpv","Title":"{\"en\":\"RGB\",\"fr\":\"\",\"de\":\"\"}","TitleDe":"","TitleFr":"","MaxN":1,"MinN":1,"Choices":["{\"en\":\"Red\"}","{\"en\":\"Green\"}","{\"en\":\"Blue\"}"],"ChoicesMap":{},"Hint":"{\"en\":\"\",\"fr\":\"\",\"de\":\"\"}","HintFr":"","HintDe":""}],"Texts":[],"Subjects":[]}]}}') +RESP=$(curl -sk "$FRONTEND_URL/api/evoting/forms" -X POST -H 'Content-Type: application/json' -b cookies.txt --data-raw $'{"Configuration":{"Title":{"En":"Colours","Fr":"","De":""},"Scaffold":[{"ID":"A7GsJxVJ","Title":{"En":"Colours","Fr":"","De":""},"Order":["GhidLIfw"],"Ranks":[],"Selects":[{"ID":"GhidLIfw","Title":{"En":"RGB","Fr":"","De":"RGB"},"MaxN":3,"MinN":1,"Choices":["{\\"en\\":\\"Red\\",\\"de\\":\\"Rot\\"}","{\\"en\\":\\"Green\\",\\"de\\":\\"Gr\xfcn\\"}","{\\"en\\":\\"Blue\\",\\"de\\":\\"Blau\\"}"],"Hint":{"En":"","Fr":"","De":"RGB"}}],"Texts":[],"Subjects":[]}]}}') FORMID=$(echo "$RESP" | jq -r .FormID) echo "add permissions - it's normal to have a timeout error after this command" @@ -43,7 +43,7 @@ if [[ "$1" ]]; then echo "Casting vote #$i" curl -sk "$FRONTEND_URL/api/evoting/forms/$FORMID/vote" -X POST -H 'Content-Type: Application/json' \ -H "Origin: $FRONTEND_URL" -b cookies.txt \ - --data-raw '{"Ballot":[{"K":[54,152,33,11,201,233,212,157,204,176,136,138,54,213,239,198,79,55,71,26,91,244,98,215,208,239,48,253,195,53,192,94],"C":[105,121,87,164,68,242,166,194,222,179,253,231,213,63,34,66,212,41,214,175,178,83,229,156,255,38,55,234,168,222,81,185]}],"UserID":null}' \ + --data-raw '{"Ballot":[{"K":[216,252,154,214,23,73,218,203,111,141,124,186,222,48,108,44,151,176,234,112,44,42,242,255,168,82,143,252,103,34,171,20],"C":[172,150,64,201,211,61,72,9,170,205,101,70,226,171,48,39,111,222,242,2,231,221,139,13,189,101,87,151,120,87,183,199]}],"UserID":null}' \ >/dev/null sleep 1 done From 97b98be2fe56ff764d8b23a1d85b42141e875329 Mon Sep 17 00:00:00 2001 From: Carine Dengler <git@carine-dengler.de> Date: Thu, 23 Nov 2023 15:29:45 +0100 Subject: [PATCH 11/11] tests: add German translations --- web/frontend/src/mocks/mockData.ts | 74 +++++++++++++++++------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/web/frontend/src/mocks/mockData.ts b/web/frontend/src/mocks/mockData.ts index 44601c3bb..a254e0871 100644 --- a/web/frontend/src/mocks/mockData.ts +++ b/web/frontend/src/mocks/mockData.ts @@ -23,18 +23,18 @@ const mockRoster: string[] = [ ]; const mockForm1: any = { - Title: { En: 'Life on the campus', Fr: 'Vie sur le campus', De: 'Life on the campus' }, + Title: { En: 'Life on the campus', Fr: 'Vie sur le campus', De: 'Leben auf dem Campus' }, Scaffold: [ { ID: (0xa2ab).toString(), - Title: { En: 'Rate the course', Fr: 'Note la course', De: 'Rate the course' }, + Title: { En: 'Rate the course', Fr: 'Note la course', De: 'Bewerten Sie den Kurs' }, Order: [(0x3fb2).toString(), (0x41e2).toString(), (0xcd13).toString(), (0xff31).toString()], Subjects: [ { Title: { En: 'Let s talk about the food', Fr: 'Parlons de la nourriture', - De: 'Let s talk about food', + De: 'Sprechen wir über das Essen', }, ID: (0xff31).toString(), Order: [(0xa319).toString(), (0x19c7).toString()], @@ -45,15 +45,15 @@ const mockForm1: any = { Title: { En: 'Select your ingredients', Fr: 'Choisi tes ingrédients', - De: 'Select your ingredients', + De: 'Wählen Sie Ihre Zutaten aus', }, ID: (0xa319).toString(), MaxN: 2, MinN: 1, Choices: [ - '{"en": "tomato", "fr": "tomate", "de": "tomato"}', - '{"en": "salad", "fr": "salade", "de": "salad"}', - '{"en": "onion", "fr": "oignon", "de": "onion"}', + '{"en": "tomato", "fr": "tomate", "de": "Tomate"}', + '{"en": "salad", "fr": "salade", "de": "Salat"}', + '{"en": "onion", "fr": "oignon", "de": "Zwiebel"}', ], Hint: { En: '', Fr: '', De: '' }, }, @@ -63,7 +63,7 @@ const mockForm1: any = { Title: { En: 'Rank the cafeteria', Fr: 'Ordonne les cafet', - De: 'Rank the cafeteria', + De: 'Ordnen Sie die Mensen', }, ID: (0x19c7).toString(), MaxN: 3, @@ -84,7 +84,7 @@ const mockForm1: any = { Title: { En: 'How did you find the provided material, from 1 (bad) to 5 (excellent) ?', Fr: 'Comment trouves-tu le matériel fourni, de 1 (mauvais) à 5 (excellent) ?', - De: 'How did you find the provided material, from 1 (bad) to 5 (excellent) ?', + De: 'Wie bewerten Sie das vorhandene Material, von 1 (schlecht) bis 5 (exzellent)?', }, ID: (0x3fb2).toString(), MaxN: 1, @@ -102,20 +102,20 @@ const mockForm1: any = { Title: { En: 'How did you find the teaching ?', Fr: 'Comment trouves-tu l enseignement ?', - De: 'How did you find the teaching ?', + De: 'Wie fanden Sie den Unterricht?', }, ID: (0x41e2).toString(), MaxN: 1, MinN: 1, Choices: [ - '{"en" : "bad", "fr": "mauvais", "de": "bad"}', - '{"en" : "normal", "fr": "normal", "de": "normal"}', - '{"en" : "good", "fr": "super", "de": "good"}', + '{"en" : "bad", "fr": "mauvais", "de": "schlecht"}', + '{"en" : "normal", "fr": "normal", "de": "durchschnittlich"}', + '{"en" : "good", "fr": "super", "de": "gut"}', ], Hint: { En: 'Be honest. This is anonymous anyway', Fr: 'Sois honnête. C est anonyme de toute façon', - De: 'Be honest. This is anonymous anyway', + De: 'Seien Sie ehrlich. Es bleibt anonym', }, }, ], @@ -124,7 +124,7 @@ const mockForm1: any = { Title: { En: 'Who were the two best TAs ?', Fr: 'Quels sont les deux meilleurs TA ?', - De: 'Who were the two best TAs ?', + De: 'Wer waren die beiden besten TutorInnen?', }, ID: (0xcd13).toString(), MaxLength: 20, @@ -169,11 +169,15 @@ const mockFormResult12: Results = { }; const mockForm2: any = { - Title: { En: 'Please give your opinion', Fr: 'Donne ton avis', De: 'Please give your opinion' }, + Title: { + En: 'Please give your opinion', + Fr: 'Donne ton avis', + De: 'Bitte sagen Sie Ihre Meinung', + }, Scaffold: [ { ID: (0xa2ab).toString(), - Title: { En: 'Rate the course', Fr: 'Note le cours', De: 'Rate the course' }, + Title: { En: 'Rate the course', Fr: 'Note le cours', De: 'Bewerten Sie den Kurs' }, Order: [(0x3fb2).toString(), (0xcd13).toString()], Selects: [ @@ -181,7 +185,7 @@ const mockForm2: any = { Title: { En: 'How did you find the provided material, from 1 (bad) to 5 (excellent) ?', Fr: 'Comment trouves-tu le matériel fourni, de 1 (mauvais) à 5 (excellent) ?', - De: 'How did you find the provided material, from 1 (bad) to 5 (excellent) ?', + De: 'Wie bewerten Sie das vorhandene Material, von 1 (schlecht) zu 5 (exzellent)?', }, ID: (0x3fb2).toString(), MaxN: 1, @@ -201,7 +205,7 @@ const mockForm2: any = { Title: { En: 'Who were the two best TAs ?', Fr: 'Quels sont les deux meilleurs TA ?', - De: 'Who were the two best TAs ?', + De: 'Wer waren die beiden besten TutorInnen?', }, ID: (0xcd13).toString(), MaxLength: 40, @@ -221,23 +225,23 @@ const mockForm2: any = { }, { ID: (0x1234).toString(), - Title: { En: 'Tough choices', Fr: 'Choix difficiles', De: 'Tough choices' }, + Title: { En: 'Tough choices', Fr: 'Choix difficiles', De: 'Schwierige Entscheidungen' }, Order: [(0xa319).toString(), (0xcafe).toString(), (0xbeef).toString()], Selects: [ { Title: { En: 'Select your ingredients', Fr: 'Choisis tes ingrédients', - De: 'Select your ingredients', + De: 'Wählen Sie Ihre Zutaten', }, ID: (0xa319).toString(), MaxN: 3, MinN: 0, Choices: [ - '{"en": "tomato", "fr": "tomate", "de": "tomato"}', - '{"en": "salad", "fr": "salade", "de": "salad"}', - '{"en": "onion", "fr": "oignon", "de": "onion"}', - '{"en": "falafel", "fr": "falafel", "de": "falafel"}', + '{"en": "tomato", "fr": "tomate", "de": "Tomate"}', + '{"en": "salad", "fr": "salade", "de": "Salat"}', + '{"en": "onion", "fr": "oignon", "de": "Zwiebel"}', + '{"en": "falafel", "fr": "falafel", "de": "Falafel"}', ], Hint: { En: '', Fr: '', De: '' }, }, @@ -248,7 +252,7 @@ const mockForm2: any = { Title: { En: 'Which cafeteria serves the best coffee ?', Fr: 'Quelle cafétéria sert le meilleur café ?', - De: 'Which cafeteria serves the best coffee ?', + De: 'Welches Café bietet den besten Kaffee an?', }, ID: (0xcafe).toString(), MaxN: 4, @@ -262,7 +266,7 @@ const mockForm2: any = { Hint: { En: '', Fr: '', De: '' }, }, { - Title: { En: 'IN or SC ?', Fr: 'IN ou SC ?', De: 'IN or SC ?' }, + Title: { En: 'IN or SC ?', Fr: 'IN ou SC ?', De: 'IN oder SC?' }, ID: (0xbeef).toString(), MaxN: 2, MinN: 1, @@ -270,7 +274,7 @@ const mockForm2: any = { Hint: { En: 'The right answer is IN ;-)', Fr: 'La bonne réponse est IN ;-)', - De: 'The right answer is IN ;-)', + De: 'Die korrekte Antwort ist IN ;-)', }, }, ], @@ -281,11 +285,15 @@ const mockForm2: any = { }; const mockForm3: any = { - Title: { En: 'Lunch', Fr: 'Déjeuner', De: 'Lunch' }, + Title: { En: 'Lunch', Fr: 'Déjeuner', De: 'Mittagessen' }, Scaffold: [ { ID: '3cVHIxpx', - Title: { En: 'Choose your lunch', Fr: 'Choisis ton déjeuner', De: 'Choose your lunch' }, + Title: { + En: 'Choose your lunch', + Fr: 'Choisis ton déjeuner', + De: 'Wählen Sie Ihr Mittagessen', + }, Order: ['PGP'], Ranks: [], Selects: [], @@ -295,7 +303,7 @@ const mockForm3: any = { Title: { En: 'Select what you want', Fr: 'Choisis ce que tu veux', - De: 'Select what you want', + De: 'Wählen Sie aus was Sie wünschen', }, MaxN: 4, MinN: 0, @@ -305,12 +313,12 @@ const mockForm3: any = { '{"en": "Firstname", "fr": "Prénom", "de": "Firstname"}', '{"en": "Main 🍕", "fr" : "Principal 🍕", "de": "Main 🍕"}', '{"en": "Drink 🧃", "fr": "Boisson 🧃", "de": "Drink 🧃"}', - '{"en":"Dessert 🍰", "fr": "Dessert 🍰", "de": "Dessert 🍰"}', + '{"en":"Dessert 🍰", "fr": "Dessert 🍰", "de": "Nachtisch 🍰"}', ], Hint: { En: 'If you change opinion call me before 11:30 a.m.', Fr: "Si tu changes d'avis appelle moi avant 11h30", - De: 'If you change opinion call me before 11:30 a.m.', + De: 'Wenn Sie Ihre Meinung ändern, rufen Sie mich vor 11:30 an', }, }, ],