diff --git a/questionservice/data/tematicas.json b/questionservice/data/tematicas.json index d54c752f..15aa1241 100644 --- a/questionservice/data/tematicas.json +++ b/questionservice/data/tematicas.json @@ -4,15 +4,42 @@ "props": ["P36", "P35", "P1344", "P37", "P47", "P2250", "P571", "P122", "P1451"], "types": ["str", "str", "str", "str", "str", "num", "date", "str", "str"], "preguntas": [ - "¿Cuál es la capital de ", - "¿Quién es el jefe de estado de ", - "¿En qué evento histórico participó ", - "¿Cuál es uno de los idiomas oficiales de ", - "¿Con qué país comparte frontera ", - "¿Cuál es la esperanza de vida media de ", - "¿En qué fecha se fundó ", - "¿Cuál es la forma de gobierno de ", - "¿Cuál es el lema de " + { + "es": "¿Cuál es la capital de %?", + "en": "What is the capital of %?" + }, + { + "es": "¿Quién es el jefe de estado de % ?", + "en": "Who is the head of state of %?" + }, + { + "es": "¿En qué evento histórico participó % ?", + "en": "In what historical event did % participate?" + }, + { + "es": "¿Cuál es uno de los idiomas oficiales de % ?", + "en": "What is one of the official languages of %?" + }, + { + "es": "¿Con qué país comparte frontera % ?", + "en": "Which country shares a border with %?" + }, + { + "es": "¿Cuál es la esperanza de vida media de % ?", + "en": "What is the average life expectancy of %?" + }, + { + "es": "¿En qué fecha se fundó % ?", + "en": "On what date was % founded?" + }, + { + "es": "¿Cuál es la forma de gobierno de % ?", + "en": "What is the form of government of %?" + }, + { + "es": "¿Cuál es el lema de % ?", + "en": "What is the motto of %?" + } ] }, "literatura": { @@ -20,10 +47,22 @@ "props": ["P50", "P577", "P136", "P674"], "types": ["str", "date", "str", "str"], "preguntas": [ - "¿Quién es el autor de la obra literaria ", - "¿Cuál fue la fecha de publicación de la obra literaria ", - "¿Cuál es el género de la obra literaria ", - "¿Cuál es uno de los personajes de la obra literaria " + { + "es": "¿Quién es el autor de la obra literaria % ?", + "en": "Who is the author of the literary work %?" + }, + { + "es": "¿Cuál fue la fecha de publicación de la obra literaria % ?", + "en": "What was the publication date of the literary work %?" + }, + { + "es": "¿Cuál es el género de la obra literaria % ?", + "en": "What is the genre of the literary work %?" + }, + { + "es": "¿Cuál es uno de los personajes de la obra literaria % ?", + "en": "Who is one of the characters in the literary work %?" + } ] }, "cine": { @@ -31,9 +70,18 @@ "props": ["P577", "P57", "P2130"], "types": ["date", "str", "num"], "preguntas": [ - "¿En qué fecha se estrenó el film ", - "¿Quién es director de la película ", - "¿Cuál fue el presupuesto (en USD) de la película " + { + "es": "¿En qué fecha se estrenó el film % ?", + "en": "On what date was the film % released?" + }, + { + "es": "¿Quién es director de la película % ?", + "en": "Who is the director of the movie %?" + }, + { + "es": "¿Cuál fue el presupuesto (en USD) de la película % ?", + "en": "What was the budget (in USD) of the movie %?" + } ] }, "arte": { @@ -41,9 +89,18 @@ "props": ["P571", "P135", "P276"], "types": ["date", "str", "str"], "preguntas": [ - "¿En qué año se pintó la obra ", - "¿A qué movimiento artístico pertenece la obra ", - "¿Dónde está exhibida la obra " + { + "es": "¿En qué año se pintó la obra % ?", + "en": "In what year was the artwork % painted?" + }, + { + "es": "¿A qué movimiento artístico pertenece la obra % ?", + "en": "To which artistic movement does the artwork % belong?" + }, + { + "es": "¿Dónde está exhibida la obra % ?", + "en": "Where is the artwork % exhibited?" + } ] }, "programacion": { @@ -51,10 +108,22 @@ "props": ["P571", "P287", "P348", "P1195"], "types": ["date", "str", "str", "str"], "preguntas": [ - "¿En qué fecha se creó el lenguaje de programación ", - "¿Quién es el creador del lenguaje de programación ", - "¿Cuál es la última versión del lenguaje de programación ", - "¿Cuál es una de las extensiones usadas por el lenguaje de programación " + { + "es": "¿En qué fecha se creó el lenguaje de programación % ?", + "en": "On what date was the programming language % created?" + }, + { + "es": "¿Quién es el creador del lenguaje de programación % ?", + "en": "Who is the creator of the programming language %?" + }, + { + "es": "¿Cuál es la última versión del lenguaje de programación % ?", + "en": "What is the latest version of the programming language %?" + }, + { + "es": "¿Cuál es una de las extensiones usadas por el lenguaje de programación % ?", + "en": "What is one of the extensions used by the programming language %?" + } ] } -} \ No newline at end of file +} diff --git a/questionservice/question-service.js b/questionservice/question-service.js index 95cfe8e7..c344ad2e 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -39,10 +39,14 @@ app.get("/questions", async (req, res) => { .status(400) .json({ error: `El límite de preguntas son ${MAX_QUESTIONS}` }); } + if(locale !== "en" && locale !== "es"){ + locale = "es"; + } try { var tematica = req.query.tematica ? req.query.tematica : "all"; var n = req.query.n ? req.query.n : 10; - var data = gen.getQuestions(tematica, n); + var locale = req.query.locale ? req.query.locale : "es"; + var data = gen.getQuestions(tematica, n, locale); res.json(data); } catch (error) { res.status(400).json({ error: error.message }); @@ -50,7 +54,10 @@ app.get("/questions", async (req, res) => { }); app.post("/questions", async (req, res) => { - const { tematicas, n } = req.body.body; + const { tematicas, n, locale } = req.body.body; + if(locale !== "en" && locale !== "es"){ + locale = "es"; + } if (!n || n > MAX_QUESTIONS) { res .status(400) @@ -64,7 +71,7 @@ app.post("/questions", async (req, res) => { ? temas : ["paises", "literatura", "cine", "arte", "programacion"]; const cantidadPreguntas = parseInt(n, 10); - const data = gen.getQuestionsPost(tematicasValidas, cantidadPreguntas); + const data = gen.getQuestionsPost(tematicasValidas, cantidadPreguntas, locale); res.json(data); } catch (error) { res.status(400).json({ error: error.message }); diff --git a/questionservice/questionGen/GeneratorChooser.js b/questionservice/questionGen/GeneratorChooser.js index 3fe1c13a..11ef1d3d 100644 --- a/questionservice/questionGen/GeneratorChooser.js +++ b/questionservice/questionGen/GeneratorChooser.js @@ -42,12 +42,12 @@ class GeneratorChooser { } } - getQuestionsPost(tematicas, n) { + getQuestionsPost(tematicas, n, locale) { var questions = []; for (let i = 0; i < n; i++) { let rand = Math.floor(Math.random() * tematicas.length); let randTematica = tematicas[rand]; - let q = this.generators.get(randTematica).generateRandomQuestions(1); + let q = this.generators.get(randTematica).generateRandomQuestions(1, locale); questions.push(q); } return questions.flat(); diff --git a/questionservice/questionGen/GenericGenerator.js b/questionservice/questionGen/GenericGenerator.js index 590369ab..056b856f 100644 --- a/questionservice/questionGen/GenericGenerator.js +++ b/questionservice/questionGen/GenericGenerator.js @@ -89,7 +89,7 @@ class GenericGenerator { }); } - generateRandomQuestion() { + generateRandomQuestion(locale) { // Elegir aleatoriamente una entidad del array var entidades = Object.keys(this.data); const entidadLabel = @@ -153,16 +153,16 @@ class GenericGenerator { } questionObj.pregunta = - this.preguntasMap.get(propiedadPregunta) + entidadLabel + "?"; + this.preguntasMap.get(propiedadPregunta)[locale].replace('%', entidadLabel); return questionObj; } - generateRandomQuestions(n) { + generateRandomQuestions(n, locale) { const questions = []; for (let i = 0; i < n; i++) { - const question = this.generateRandomQuestion(); + const question = this.generateRandomQuestion(locale); questions.push(question); } diff --git a/webapp/package-lock.json b/webapp/package-lock.json index f93cbfe5..b9fe9ee3 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -19,8 +19,10 @@ "@tsparticles/slim": "^3.3.0", "axios": "^1.6.5", "framer-motion": "^11.0.19", + "i18next": "^23.10.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^14.1.0", "react-router-dom": "^6.22.0", "react-scripts": "^5.0.1", "web-vitals": "^3.5.1" @@ -11337,6 +11339,14 @@ "node": ">= 12" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.0", "license": "MIT", @@ -11471,6 +11481,28 @@ "node": ">=10.17.0" } }, + "node_modules/i18next": { + "version": "23.10.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.10.1.tgz", + "integrity": "sha512-NDiIzFbcs3O9PXpfhkjyf7WdqFn5Vq6mhzhtkXzj51aOcNuPNcTwuYNuXCpHsanZGHlHKL35G7huoFeVic1hng==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "license": "MIT", @@ -18568,6 +18600,27 @@ } } }, + "node_modules/react-i18next": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.0.tgz", + "integrity": "sha512-3KwX6LHpbvGQ+sBEntjV4sYW3Zovjjl3fpoHbUwSgFHf0uRBcbeCBLR5al6ikncI5+W0EFb71QXZmfop+J6NrQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "17.0.2", "license": "MIT" @@ -23216,6 +23269,14 @@ "node": ">= 0.8" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "license": "MIT", @@ -31280,6 +31341,14 @@ } } }, + "html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "requires": { + "void-elements": "3.1.0" + } + }, "html-webpack-plugin": { "version": "5.6.0", "requires": { @@ -31352,6 +31421,14 @@ "human-signals": { "version": "2.1.0" }, + "i18next": { + "version": "23.10.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.10.1.tgz", + "integrity": "sha512-NDiIzFbcs3O9PXpfhkjyf7WdqFn5Vq6mhzhtkXzj51aOcNuPNcTwuYNuXCpHsanZGHlHKL35G7huoFeVic1hng==", + "requires": { + "@babel/runtime": "^7.23.2" + } + }, "iconv-lite": { "version": "0.6.3", "requires": { @@ -35760,6 +35837,15 @@ "use-sidecar": "^1.1.2" } }, + "react-i18next": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.0.tgz", + "integrity": "sha512-3KwX6LHpbvGQ+sBEntjV4sYW3Zovjjl3fpoHbUwSgFHf0uRBcbeCBLR5al6ikncI5+W0EFb71QXZmfop+J6NrQ==", + "requires": { + "@babel/runtime": "^7.23.9", + "html-parse-stringify": "^3.0.1" + } + }, "react-is": { "version": "17.0.2" }, @@ -38783,6 +38869,11 @@ "vary": { "version": "1.1.2" }, + "void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==" + }, "w3c-hr-time": { "version": "1.0.2", "requires": { diff --git a/webapp/package.json b/webapp/package.json index eba63178..cd317f9c 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -14,8 +14,10 @@ "@tsparticles/slim": "^3.3.0", "axios": "^1.6.5", "framer-motion": "^11.0.19", + "i18next": "^23.10.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^14.1.0", "react-router-dom": "^6.22.0", "react-scripts": "^5.0.1", "web-vitals": "^3.5.1" diff --git a/webapp/src/App.test.js b/webapp/src/App.test.js index 1595e165..3d6feb2d 100644 --- a/webapp/src/App.test.js +++ b/webapp/src/App.test.js @@ -1,96 +1,112 @@ -import { render, screen, fireEvent } from '@testing-library/react'; -import { BrowserRouter as Router} from 'react-router-dom'; -import Home from './pages/Home/Home.js'; -import Nav from './components/Nav/Nav.js'; -import Footer from './components/Footer/Footer.js'; -import App from './App'; - -describe('App Component', () => { - test('renders login page by default', () => { +import { render, screen, fireEvent } from "@testing-library/react"; +import { BrowserRouter as Router } from "react-router-dom"; +import Home from "./pages/Home/Home.js"; +import Nav from "./components/Nav/Nav.js"; +import Footer from "./components/Footer/Footer.js"; +import App from "./App"; +import { I18nextProvider } from "react-i18next"; +import i18n from "./i18n.js"; + +describe("App Component", () => { + test("renders login page by default", () => { render( - + + + ); - const loginPage = screen.getByText('Login'); + const loginPage = screen.getByText("Login"); expect(loginPage).toBeInTheDocument(); }); - }); -describe('Home Component', () => { - test('renders welcome message and game links', () => { +describe("Home Component", () => { + test("renders welcome message and game links", () => { const { getByText, getByRole } = render( - - - + + + + + ); // Verifica que el mensaje de bienvenida esté presente - expect(getByText('¡Bienvenido a WIQ!')).toBeInTheDocument(); - expect(getByText('Elige el modo de juego')).toBeInTheDocument(); + expect(getByText("¡Bienvenido a WIQ!")).toBeInTheDocument(); + expect(getByText("Elige el modo de juego")).toBeInTheDocument(); // Verifica el texto de cada enlace - expect(screen.getByText('Clásico')).toBeInTheDocument(); - expect(screen.getByText('Batería de sabios')).toBeInTheDocument(); + expect(getByRole("button", { name: "Clásico" })).toBeInTheDocument(); + expect(getByRole("button", { name: "Batería de sabios" })).toBeInTheDocument(); + expect(getByRole("button", { name: "Calculadora humana" })).toBeInTheDocument(); }); }); -describe('Nav Component', () => { - it('renders Nav component with links and logout button', () => { - const { getByText, getByRole } = render( - -