diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 086b7f76..43911341 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,6 +19,9 @@ jobs: - run: npm --prefix webapp ci - run: npm --prefix questiongenerator ci - run: npm --prefix gamehistoryservice ci + - run: npm --prefix apis/allquestionservice ci + - run: npm --prefix apis/alluserservice ci + - run: npm --prefix perfilservice ci - run: npm --prefix users/authservice test -- --coverage - run: npm --prefix users/userservice test -- --coverage - run: npm --prefix gatewayservice test -- --coverage diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c396a5d3..56c71a56 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -155,10 +155,62 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} registry: ghcr.io workdir: gamehistoryservice + docker-push-perfilservice: + name: Push perfil service service Docker Image to GitHub Packages + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + needs: [e2e-tests] + steps: + - uses: actions/checkout@v4 + - name: Publish to Registry + uses: elgohr/Publish-Docker-Github-Action@v5 + with: + name: arquisoft/wiq_es2c/perfilservice + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + registry: ghcr.io + workdir: perfilservice + docker-push-allquestionservice: + name: Push all question service service Docker Image to GitHub Packages + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + needs: [e2e-tests] + steps: + - uses: actions/checkout@v4 + - name: Publish to Registry + uses: elgohr/Publish-Docker-Github-Action@v5 + with: + name: arquisoft/wiq_es2c/apis/allquestionservice + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + registry: ghcr.io + workdir: apis/allquestionservice + docker-push-alluserservice: + name: Push all user service service Docker Image to GitHub Packages + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + needs: [e2e-tests] + steps: + - uses: actions/checkout@v4 + - name: Publish to Registry + uses: elgohr/Publish-Docker-Github-Action@v5 + with: + name: arquisoft/wiq_es2c/apis/alluserservice + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + registry: ghcr.io + workdir: apis/alluserservice deploy: name: Deploy over SSH runs-on: ubuntu-latest - needs: [docker-push-userservice,docker-push-authservice,docker-push-gatewayservice,docker-push-webapp,docker-push-questiongenerator,docker-push-gamehistoryservice] + needs: [docker-push-userservice,docker-push-authservice,docker-push-gatewayservice,docker-push-webapp, + docker-push-questiongenerator,docker-push-gamehistoryservice,docker-push-perfilservice,docker-push-allquestionservice,docker-push-alluserservice] steps: - name: Deploy over SSH uses: fifsky/ssh-action@master diff --git a/apis/allquestionservice/Dockerfile b/apis/allquestionservice/Dockerfile index 5678a0ab..74b03e40 100644 --- a/apis/allquestionservice/Dockerfile +++ b/apis/allquestionservice/Dockerfile @@ -13,8 +13,11 @@ RUN npm install # Copy the app source code to the working directory COPY . . -# Expose the port the app runs on +# Expose the port the app runs on EXPOSE 8007 +ARG API_ORIGIN_URI="http://localhost:3000" +ENV REACT_APP_API_ORIGIN_ENDPOINT=$API_ORIGIN_URI + # Define the command to run your app CMD ["node", "allquestions-api.js"] diff --git a/apis/alluserservice/Dockerfile b/apis/alluserservice/Dockerfile index d1060ce1..2b562cbc 100644 --- a/apis/alluserservice/Dockerfile +++ b/apis/alluserservice/Dockerfile @@ -16,5 +16,8 @@ COPY . . # Expose the port the app runs on EXPOSE 8006 +ARG API_ORIGIN_URI="http://localhost:3000" +ENV REACT_APP_API_ORIGIN_ENDPOINT=$API_ORIGIN_URI + # Define the command to run your app CMD ["node", "allusers-api.js"] diff --git a/docker-compose.yml b/docker-compose.yml index e4f079ac..080e477b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -77,9 +77,11 @@ services: perfilservice: container_name: perfilservice-${teamname:-defaultASW} - image: ghcr.io/arquisoft/wiq_es2c/perfilservice/perfil-api:latest + image: ghcr.io/arquisoft/wiq_es2c/perfilservice:latest profiles: ["dev", "prod"] build: ./perfilservice + volumes: + - mongodb_data:/data/db depends_on: - mongodb ports: @@ -91,9 +93,11 @@ services: alluserservice: container_name: alluserservice-${teamname:-defaultASW} - image: ghcr.io/arquisoft/wiq_es2c/apis/alluserservice/allusers-api:latest + image: ghcr.io/arquisoft/wiq_es2c/apis/alluserservice:latest profiles: ["dev", "prod"] build: ./apis/alluserservice + volumes: + - mongodb_data:/data/db depends_on: - mongodb ports: @@ -105,9 +109,11 @@ services: allquestionservice: container_name: allquestionservice-${teamname:-defaultASW} - image: ghcr.io/arquisoft/wiq_es2c/apis/allquestionservice/allquestions-api:latest + image: ghcr.io/arquisoft/wiq_es2c/apis/allquestionservice:latest profiles: ["dev", "prod"] build: ./apis/allquestionservice + volumes: + - mongodb_data:/data/db depends_on: - mongodb ports: diff --git a/package-lock.json b/package-lock.json index 1ffe46df..e003d34f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2,5 +2,49 @@ "name": "wiq_es2c", "lockfileVersion": 3, "requires": true, - "packages": {} + "packages": { + "": { + "dependencies": { + "i18next": "^23.11.2" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/i18next": { + "version": "23.11.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.2.tgz", + "integrity": "sha512-qMBm7+qT8jdpmmDw/kQD16VpmkL9BdL+XNAK5MNbNFaf1iQQq35ZbPrSlqmnNPOSUY4m342+c0t0evinF5l7sA==", + "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/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + } + } } diff --git a/package.json b/package.json new file mode 100644 index 00000000..dc54a203 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "i18next": "^23.11.2" + } +} diff --git a/perfilservice/Dockerfile b/perfilservice/Dockerfile index 997b1fe3..889973d2 100644 --- a/perfilservice/Dockerfile +++ b/perfilservice/Dockerfile @@ -16,5 +16,8 @@ COPY . . # Expose the port the app runs on EXPOSE 8005 +ARG API_ORIGIN_URI="http://localhost:3000" +ENV REACT_APP_API_ORIGIN_ENDPOINT=$API_ORIGIN_URI + # Define the command to run your app CMD ["node", "perfil-api.js"] diff --git a/timerservice/timer.js b/timerservice/timer.js deleted file mode 100644 index 684792ad..00000000 --- a/timerservice/timer.js +++ /dev/null @@ -1,29 +0,0 @@ - -app.use((req, res, next) => { - res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); - res.setHeader('Access-Control-Allow-Credentials', true); - next(); -}); - -app.get('/timer', async (req, res) => { - - const time = parseInt(req.params.time); - startTimer(time,() =>{ - res.send("Time up"); - }) - -}); - -function startTimer(time,func){ - this.timer = setTimeout(()=>{ - func(); - },time) -} - -var server = app.listen(port, () => { - console.log(`Questions Generation Service listening at http://localhost:${port}`); -}); - -module.exports = server \ No newline at end of file diff --git a/users/userservice/user-service.test.js b/users/userservice/user-service.test.js index 71f0d8ec..4c8b1ff7 100644 --- a/users/userservice/user-service.test.js +++ b/users/userservice/user-service.test.js @@ -2,7 +2,7 @@ const request = require('supertest'); const { MongoMemoryServer } = require('mongodb-memory-server'); let mongoServer; -let +let newString = "testadduser"; const newUser = { username: 'testuser', email: 'testuser@gmail.com', diff --git a/webapp/e2e/steps/register-form.steps.js b/webapp/e2e/steps/register-form.steps.js index d411994d..447026b0 100644 --- a/webapp/e2e/steps/register-form.steps.js +++ b/webapp/e2e/steps/register-form.steps.js @@ -30,16 +30,16 @@ defineFeature(feature, test => { given('An unregistered user', async () => { username = "pablo" - email = "pablo@email.com" + email = "pablo@gmail.com" password = "pabloasw" await expect(page).toClick("button", { text: "¿No tienes cuenta? Registrate aquí." }); }); - + when('I fill the data in the form and press submit', async () => { await expect(page).toFill('input[name="username"]', username); await expect(page).toFill('input[name="email"]', email); await expect(page).toFill('input[name="password"]', password); - await expect(page).toClick('button', { text: 'REGÍSTRATE' }) + await expect(page).toClick('button', { text: 'REGÍSTRATE' }); }); then('A confirmation message should be shown in the screen', async () => { diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 69590fe3..9664032b 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -16,8 +16,11 @@ "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.2", "axios": "^1.6.5", + "i18next": "^23.11.2", + "i18next-browser-languagedetector": "^7.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^14.1.0", "react-router-dom": "^6.22.1", "react-scripts": "5.0.1", "web-vitals": "^3.5.1" @@ -25,11 +28,14 @@ "devDependencies": { "axios-mock-adapter": "^1.22.0", "expect-puppeteer": "^9.0.2", + "i18next": "^23.11.2", + "i18next-browser-languagedetector": "^7.2.1", "jest": "^29.3.1", "jest-cucumber": "^3.0.1", "jest-environment-node": "^29.7.0", "mongodb-memory-server": "^9.1.4", "puppeteer": "^21.7.0", + "react-i18next": "^14.1.0", "serve": "^14.2.1", "start-server-and-test": "^2.0.3" } @@ -12137,6 +12143,15 @@ "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==", + "dev": true, + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", @@ -12280,6 +12295,38 @@ "node": ">=10.17.0" } }, + "node_modules/i18next": { + "version": "23.11.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.2.tgz", + "integrity": "sha512-qMBm7+qT8jdpmmDw/kQD16VpmkL9BdL+XNAK5MNbNFaf1iQQq35ZbPrSlqmnNPOSUY4m342+c0t0evinF5l7sA==", + "dev": true, + "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/i18next-browser-languagedetector": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz", + "integrity": "sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -22031,6 +22078,28 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "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==", + "dev": true, + "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", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -26647,6 +26716,15 @@ "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", diff --git a/webapp/package.json b/webapp/package.json index 4825bfc3..2b7867a6 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -5,14 +5,17 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", - "@mui/material": "^5.15.3", "@mui/icons-material": "^5.15.15", + "@mui/material": "^5.15.3", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.2", "axios": "^1.6.5", + "i18next": "^23.11.2", + "i18next-browser-languagedetector": "^7.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^14.1.0", "react-router-dom": "^6.22.1", "react-scripts": "5.0.1", "web-vitals": "^3.5.1" @@ -52,6 +55,9 @@ "mongodb-memory-server": "^9.1.4", "puppeteer": "^21.7.0", "serve": "^14.2.1", - "start-server-and-test": "^2.0.3" + "start-server-and-test": "^2.0.3", + "i18next": "^23.11.2", + "i18next-browser-languagedetector": "^7.2.1", + "react-i18next": "^14.1.0" } } diff --git a/webapp/src/App.js b/webapp/src/App.js index 99bb223b..66589424 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -7,6 +7,9 @@ import Typography from '@mui/material/Typography'; import Link from '@mui/material/Link'; import './App.css'; import { createTheme, ThemeProvider } from '@mui/material/styles'; +import {Button, Tooltip } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + const theme = createTheme({ palette: { @@ -16,7 +19,12 @@ const theme = createTheme({ }, }); + + function App() { + + const [t, i18n] = useTranslation("global"); + const [showLogin, setShowLogin] = useState(true); const handleToggleView = () => { @@ -39,14 +47,31 @@ function App() { {showLogin ? ( - ¿No tienes cuenta? Registrate aquí. + {t("enlaceLogin")} ) : ( - ¿Ya tienes cuenta? Inicia sesión aquí. + {t("enlaceRegistro")} )} +
+ {/* Aquí coloca tus dos botones */} + + + + + + +
); diff --git a/webapp/src/App.test.js b/webapp/src/App.test.js index 1bfc84bf..611a3051 100644 --- a/webapp/src/App.test.js +++ b/webapp/src/App.test.js @@ -2,6 +2,10 @@ import { render, screen, act, fireEvent} from '@testing-library/react'; import App from './App'; import { BrowserRouter as Router } from 'react-router-dom'; import { UserProvider } from './components/UserContext'; +import { I18nextProvider } from "react-i18next"; +import i18n from "./translations/i18n"; + +i18n.changeLanguage("es"); describe('renders learn react link', () => { it('play', async () => { @@ -10,11 +14,13 @@ describe('renders learn react link', () => { showLogin = newState; }; - render( - - - - ); + render( + + + + + + ); const button = screen.getByText("¿No tienes cuenta? Registrate aquí."); expect(button).toBeInTheDocument(); diff --git a/webapp/src/components/AddUser.js b/webapp/src/components/AddUser.js index 0b8e69aa..c2e43e6a 100644 --- a/webapp/src/components/AddUser.js +++ b/webapp/src/components/AddUser.js @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import axios from 'axios'; import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; import '../App.css'; +import { useTranslation } from 'react-i18next'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; @@ -15,6 +16,8 @@ const AddUser = () => { const [openSnackbar, setOpenSnackbar] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(''); + const [t] = useTranslation("global"); + const addUser = async () => { try { @@ -34,23 +37,23 @@ const AddUser = () => { return ( + sx={{ + marginTop: 4, + borderRadius: '10px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }}>
- REGÍSTRATE + {t("registro")} setUsername(e.target.value)} sx={{ marginBottom: 4, backgroundColor: '#FFFFFF'}} @@ -59,7 +62,7 @@ const AddUser = () => { name="email" margin="normal" fullWidth - label="Email" + label={t("email")} value={email} onChange={(e) => setEmail(e.target.value)} sx={{ marginBottom: 4, backgroundColor: '#FFFFFF'}} @@ -68,7 +71,7 @@ const AddUser = () => { name="password" margin="normal" fullWidth - label="Contraseña" + label={t("password")} type="password" value={password} onChange={(e) => setPassword(e.target.value)} @@ -76,7 +79,7 @@ const AddUser = () => { /> diff --git a/webapp/src/components/AddUser.test.js b/webapp/src/components/AddUser.test.js index 3e8403c6..30a0658d 100644 --- a/webapp/src/components/AddUser.test.js +++ b/webapp/src/components/AddUser.test.js @@ -3,7 +3,10 @@ import { render, fireEvent, screen, waitFor } from '@testing-library/react'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import AddUser from './AddUser'; +import { I18nextProvider } from "react-i18next"; +import i18n from "../translations/i18n"; +i18n.changeLanguage("es"); const mockAxios = new MockAdapter(axios); describe('AddUser component', () => { @@ -12,7 +15,9 @@ describe('AddUser component', () => { }); it('should add user successfully', async () => { - render(); + render( + + ); const usernameInput = screen.getByLabelText('Usuario'); const passwordInput = screen.getByLabelText('Contraseña'); @@ -35,7 +40,9 @@ describe('AddUser component', () => { }); it('should handle error when adding user', async () => { - render(); + render( + + ); const usernameInput = screen.getByLabelText('Usuario'); const passwordInput = screen.getByLabelText('Contraseña'); diff --git a/webapp/src/components/AllQuestions.js b/webapp/src/components/AllQuestions.js index d031033b..223ec1aa 100644 --- a/webapp/src/components/AllQuestions.js +++ b/webapp/src/components/AllQuestions.js @@ -1,11 +1,13 @@ import axios from 'axios'; import React, { useState, useEffect, useCallback } from 'react'; import { Container, Typography, TableContainer, Table, TableHead, TableBody, TableRow, TableCell, Paper, Snackbar } from '@mui/material'; +import { useTranslation } from 'react-i18next'; const AllQuestions = () => { - const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; + const [t] = useTranslation("global"); + const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; const [questions, setQuestions] = useState([]); const [error, setError] = useState(''); @@ -17,7 +19,7 @@ const AllQuestions = () => { } catch (error) { setError(error.response.data.error); } - }) + }, [apiEndpoint]); useEffect(() => { getAllQuestions(); @@ -38,14 +40,14 @@ const AllQuestions = () => { width: '100%', }}> - TODAS LAS PREGUNTAS + {t("textoAllQuestions")} - Pregunta - Respuesta + {t("textoPregunta")} + {t("textoRespuesta")} diff --git a/webapp/src/components/AllUsers.js b/webapp/src/components/AllUsers.js index 57dfdd92..fd69f284 100644 --- a/webapp/src/components/AllUsers.js +++ b/webapp/src/components/AllUsers.js @@ -1,9 +1,13 @@ import axios from 'axios'; import React, { useState, useEffect, useCallback } from 'react'; import { Container, Typography, TableContainer, Table, TableHead, TableBody, TableRow, TableCell, Paper, Snackbar } from '@mui/material'; +import { useTranslation } from 'react-i18next'; const AllUsers = () => { + const [t] = useTranslation("global"); + + const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; @@ -18,7 +22,7 @@ const AllUsers = () => { } catch (error) { setError(error.response.data.error); } - }) + }, [apiEndpoint]); useEffect(() => { getAllUsers(); @@ -39,15 +43,15 @@ const AllUsers = () => { width: '100%', }}> - TODOS LOS USUARIOS + {t("textoAllUsers")}
- Usuario - Email - Creado + {t("usuario")} + {t("email")} + {t("creado")} diff --git a/webapp/src/components/EndGame.js b/webapp/src/components/EndGame.js index 48e1a7ae..a0b2b11c 100644 --- a/webapp/src/components/EndGame.js +++ b/webapp/src/components/EndGame.js @@ -90,6 +90,11 @@ const EndGame = () => { +
+ {error && ( + setError('')} message={`Error: ${error}`} /> + )} +
); }; diff --git a/webapp/src/components/Game.js b/webapp/src/components/Game.js index 8fb89d50..dd5eb6a0 100644 --- a/webapp/src/components/Game.js +++ b/webapp/src/components/Game.js @@ -5,10 +5,14 @@ import { Container, Typography, Button, Snackbar } from '@mui/material'; import { useNavigate, useLocation } from 'react-router-dom'; import { useUser } from './UserContext'; import '../App.css'; +import { useTranslation } from 'react-i18next'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; const Game = () => { + + const [t] = useTranslation("global"); + const { usernameGlobal } = useUser(); const [question, setQuestion] = useState(''); const [image, setImage] = useState(''); @@ -49,7 +53,7 @@ const Game = () => { console.log("Error: " + error.response.data.error); setError(error.response.data.error); } - }, [usernameGlobal]) + }, [usernameGlobal, MAX_TIME, THEMATIC]); const saveGameHistory = useCallback(async () => { try { @@ -88,7 +92,7 @@ const Game = () => { return () => { clearTimeout(timerId); } - }, [elapsedTime, getQuestion, answeredQuestions, navigate, isTimeRunning, saveGameHistory]); + }, [elapsedTime, getQuestion, answeredQuestions, navigate, isTimeRunning, saveGameHistory, MAX_PREGUNTAS]); const handleOptionClick = async (option) => { var isTheCorrectAnswer = false; @@ -147,7 +151,7 @@ const Game = () => { {answeredQuestions} / {MAX_PREGUNTAS} - Tiempo restante: {elapsedTime} segundos + {t("textoTiempoRest")} {elapsedTime} {t("textoTiempoRest2")} { beforeEach(() => { mockAxios.reset(); @@ -37,11 +46,13 @@ describe('Start game', () => { { message: "Tiempo de pregunta actualizado exitosamente", updatedQuestion }); - render( - - - - ); + render( + + + + + + ); var button1; var button2; @@ -108,11 +119,13 @@ describe('Start game', () => { { message: "Tiempo de pregunta actualizado exitosamente", updatedQuestion }); - render( - - - - ); + render( + + + + + + ); await waitFor(() => { expect(screen.getByText('Error: Error al generar la pregunta')).toBeInTheDocument(); diff --git a/webapp/src/components/GameConfiguration.js b/webapp/src/components/GameConfiguration.js index dd1d4eae..a3a7c222 100644 --- a/webapp/src/components/GameConfiguration.js +++ b/webapp/src/components/GameConfiguration.js @@ -2,12 +2,16 @@ import React, { useState } from 'react'; import axios from 'axios'; import { useNavigate} from 'react-router-dom'; -import { Container, Typography, TextField, Button, Snackbar, skeletonClasses } from '@mui/material'; +import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; import '../App.css'; +import { useTranslation } from 'react-i18next'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; const GameConfiguration = () => { + + const [t] = useTranslation("global"); + const navigate = useNavigate(); const [openSnackbar, setOpenSnackbar] = useState(false); const [error, setError] = useState(''); @@ -49,6 +53,7 @@ const GameConfiguration = () => { navigate("/Game", {state: {time: valueTime, question:valueQuestion, thematic:selectedOption}}); } catch (error) { setError(error.response.data.error); + setSnackbarMessage(error); setOpenSnackbar(true); } }; @@ -60,22 +65,22 @@ const GameConfiguration = () => { return ( + sx={{ + marginTop: 25, + borderRadius: '10px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }}>
- Personaliza tu partida + {t("textoPersonalizar")} { name="time" margin="normal" fullWidth - label="Tiempo por pregunta" + label={t("textoTiempoPreg")} onChange={handleChangeTime} value={valueTime} type="number" @@ -106,20 +111,20 @@ const GameConfiguration = () => { }} /> - Selecciona las tematicas de la pregunta para poder jugar + {t("textoTematicas")}
{error && ( diff --git a/webapp/src/components/Gamehistory.js b/webapp/src/components/Gamehistory.js index e17b3975..2dc6a406 100644 --- a/webapp/src/components/Gamehistory.js +++ b/webapp/src/components/Gamehistory.js @@ -3,10 +3,14 @@ import axios from 'axios'; import { useUser } from './UserContext'; import { Container, Typography, TableContainer, Table, TableHead, TableBody, TableRow, TableCell, Paper, Snackbar } from '@mui/material'; import '../App.css'; +import { useTranslation } from 'react-i18next'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; const Gamehistory = () => { + + const [t] = useTranslation("global"); + const { usernameGlobal } = useUser(); const [gamehistory, setGameHistory] = useState(''); const [error, setError] = useState(''); @@ -39,18 +43,18 @@ const Gamehistory = () => { width: '100%', }}> - MIS ESTADÍSTICAS + {t("textoHistorico")}
- Partidas Jugadas - Preguntas respondidas - Aciertos - Fallos - Ratio de Acierto - Tiempo jugado + {t("textoPartJug")} + {t("textoPregResp")} + {t("textoAciertos")} + {t("textoFallos")} + {t("textoRatAc")} + {t("textoTiempo")} diff --git a/webapp/src/components/Gamehistory.test.js b/webapp/src/components/Gamehistory.test.js index 8c4d19f4..9bf6e584 100644 --- a/webapp/src/components/Gamehistory.test.js +++ b/webapp/src/components/Gamehistory.test.js @@ -5,7 +5,10 @@ import { UserProvider } from './UserContext'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import GameHistory from './Gamehistory'; +import { I18nextProvider } from "react-i18next"; +import i18n from "../translations/i18n"; +i18n.changeLanguage("es"); const mockAxios = new MockAdapter(axios); describe('Game history', () => { @@ -24,20 +27,22 @@ describe('Game history', () => { ratio: "60 %", totalTime: "10 s"}); - render( - - - - ); + render( + + + + + + ); await waitFor(() => { expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('MIS ESTADÍSTICAS'); - expect(screen.getByText('Partidas Jugadas')).toBeInTheDocument(); + expect(screen.getByText('Partidas jugadas')).toBeInTheDocument(); expect(screen.getByText('Preguntas respondidas')).toBeInTheDocument(); expect(screen.getByText('Aciertos')).toBeInTheDocument(); expect(screen.getByText('Fallos')).toBeInTheDocument(); - expect(screen.getByText('Ratio de Acierto')).toBeInTheDocument(); + expect(screen.getByText('Ratio de acierto')).toBeInTheDocument(); expect(screen.getByText('Tiempo jugado')).toBeInTheDocument(); expect(screen.getByText('1')).toBeInTheDocument(); diff --git a/webapp/src/components/Login.js b/webapp/src/components/Login.js index 21214930..3dfb2508 100644 --- a/webapp/src/components/Login.js +++ b/webapp/src/components/Login.js @@ -5,8 +5,13 @@ import axios from 'axios'; import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; import { useUser } from './UserContext'; import '../App.css'; +import { useTranslation } from 'react-i18next'; + const Login = () => { + + const [t] = useTranslation("global"); + const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); @@ -54,28 +59,30 @@ const Login = () => { setOpenSnackbar(false); }; + + return ( + sx={{ + marginTop: 4, + borderRadius: '10px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }}> {loginSuccess ? ( null ) : (
- Inicia sesión + {t("login")} setUsername(e.target.value)} sx={{ marginBottom: 4, backgroundColor: '#FFFFFF'}} @@ -83,16 +90,16 @@ const Login = () => { setPassword(e.target.value)} sx={{ marginBottom: 4, backgroundColor: '#FFFFFF'}} /> - + {error && ( setError('')} message={`Error: ${error}`} /> )} diff --git a/webapp/src/components/Login.test.js b/webapp/src/components/Login.test.js index 5e45e73f..c600e01e 100644 --- a/webapp/src/components/Login.test.js +++ b/webapp/src/components/Login.test.js @@ -5,8 +5,10 @@ import MockAdapter from 'axios-mock-adapter'; import Login from './Login'; import { BrowserRouter as Router } from 'react-router-dom'; import { UserProvider } from './UserContext'; +import { I18nextProvider } from "react-i18next"; +import i18n from "../translations/i18n"; - +i18n.changeLanguage("es"); const mockAxios = new MockAdapter(axios); describe('Login component', () => { @@ -15,11 +17,13 @@ describe('Login component', () => { }); it('should log in successfully', async () => { - render( - - - - ); + render( + + + + + + ); const usernameInput = screen.getByLabelText('Usuario'); const passwordInput = screen.getByLabelText('Contraseña'); @@ -38,11 +42,13 @@ describe('Login component', () => { }); it('should handle error when logging in', async () => { - render( - - - - ); + render( + + + + + + ); const usernameInput = screen.getByLabelText('Usuario'); const passwordInput = screen.getByLabelText('Contraseña'); diff --git a/webapp/src/components/PantallaInicio.js b/webapp/src/components/PantallaInicio.js index 3140c423..344aaabc 100644 --- a/webapp/src/components/PantallaInicio.js +++ b/webapp/src/components/PantallaInicio.js @@ -4,10 +4,15 @@ import { useUser } from './UserContext'; import { useNavigate } from 'react-router-dom'; import NewGameIcon from '@mui/icons-material/SportsEsports'; import '../App.css'; +import { useTranslation } from 'react-i18next'; + const PantallaInicio = () => { + const [t] = useTranslation("global"); + + const [openSnackbar, setOpenSnackbar] = useState(false); const [error, setError] = useState(''); @@ -41,12 +46,12 @@ const PantallaInicio = () => { alignItems: 'center' }}> - ¡BIENVENIDO A WIQ {usernameGlobal}! + {t("textoInicio")} {usernameGlobal}! diff --git a/webapp/src/components/PantallaInicio.test.js b/webapp/src/components/PantallaInicio.test.js index fbf59da5..9df57bcb 100644 --- a/webapp/src/components/PantallaInicio.test.js +++ b/webapp/src/components/PantallaInicio.test.js @@ -5,7 +5,10 @@ import MockAdapter from 'axios-mock-adapter'; import PantallaInicio from './PantallaInicio'; import { BrowserRouter as Router } from 'react-router-dom'; import { UserProvider } from './UserContext'; +import { I18nextProvider } from "react-i18next"; +import i18n from "../translations/i18n"; +i18n.changeLanguage("es"); const mockAxios = new MockAdapter(axios); describe('PantallaInicio component', () => { @@ -15,13 +18,15 @@ describe('PantallaInicio component', () => { it('muestra la pantalla de inicio correctamente', async () => { - render( - - - - ); + render( + + + + + + ); - const element = screen.getByText(/¡BIENVENIDO A WIQ/); + const element = screen.getByText(/BIENVENIDO/); const nuevaPartidaButton = screen.getByRole('button', { name: 'NUEVA PARTIDA' }); // Verifica si el elemento se encuentra en el DOM diff --git a/webapp/src/components/PantallaInicioAdmin.js b/webapp/src/components/PantallaInicioAdmin.js index 699dbbd0..a1941388 100644 --- a/webapp/src/components/PantallaInicioAdmin.js +++ b/webapp/src/components/PantallaInicioAdmin.js @@ -1,10 +1,14 @@ import React, { useState } from 'react'; import { Container, Button, Box, Snackbar } from '@mui/material'; import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; const PantallaInicio = () => { + const [t] = useTranslation("global"); + + const [openSnackbar, setOpenSnackbar] = useState(false); const [error, setError] = useState(''); @@ -43,13 +47,13 @@ const PantallaInicio = () => { alignItems: 'center' }}> - + {error && ( setError('')} message={`Error: ${error}`} /> )} diff --git a/webapp/src/components/Perfil.js b/webapp/src/components/Perfil.js index c3e9be09..eb18ad12 100644 --- a/webapp/src/components/Perfil.js +++ b/webapp/src/components/Perfil.js @@ -2,9 +2,13 @@ import axios from 'axios'; import React, { useState, useEffect, useCallback } from 'react'; import { Container, Typography, TableContainer, Table, TableHead, TableBody, TableRow, TableCell, Paper, Snackbar } from '@mui/material'; import { useUser } from './UserContext'; +import { useTranslation } from 'react-i18next'; + const Perfil = () => { + const [t] = useTranslation("global"); + const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; const { usernameGlobal } = useUser(); @@ -24,7 +28,7 @@ const Perfil = () => { } catch (error) { setError(error.response.data.error); } - }, [usernameGlobal]) + }, [usernameGlobal, apiEndpoint]); useEffect(() => { getPerfil(); @@ -46,15 +50,15 @@ const Perfil = () => { }} > - PERFIL + {t("textoPerfil")}
- Usuario - Email - Creado + {t("usuario")} + {t("email")} + {t("creado")} diff --git a/webapp/src/components/Ranking.js b/webapp/src/components/Ranking.js index b190f0cf..4a4497bb 100644 --- a/webapp/src/components/Ranking.js +++ b/webapp/src/components/Ranking.js @@ -1,14 +1,15 @@ import React, { useState, useEffect, useCallback } from 'react'; import axios from 'axios'; -import { Container, Box, Typography, Grid} from '@mui/material'; -import { useUser } from './UserContext'; +import { Container, Box, Typography, Grid, Snackbar} from '@mui/material'; import '../App.css'; +import { useTranslation } from 'react-i18next'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; const Ranking = () => { - const { usernameGlobal } = useUser(); + const [t] = useTranslation("global"); + const [ranking, setRanking] = useState(''); const [error, setError] = useState(''); @@ -19,7 +20,7 @@ const Ranking = () => { } catch (error) { setError(error.response.data.error); } - }, [usernameGlobal]) + }, []); useEffect(() => { getRanking(); @@ -37,7 +38,7 @@ const Ranking = () => { }}> - Top 3 Usuarios + {t("textoTop")} @@ -68,6 +69,11 @@ const Ranking = () => { +
+ {error && ( + setError('')} message={`Error: ${error}`} /> + )} +
); }; diff --git a/webapp/src/components/fragments/NavigationBar.js b/webapp/src/components/fragments/NavigationBar.js index 20f84362..d1093103 100644 --- a/webapp/src/components/fragments/NavigationBar.js +++ b/webapp/src/components/fragments/NavigationBar.js @@ -5,9 +5,13 @@ import { AppBar, Toolbar, IconButton, Menu, MenuItem, Grid, Button, Hidden} from import Tooltip from '@mui/material/Tooltip'; import { useUser } from '../UserContext'; import MenuIcon from '@mui/icons-material/Menu'; +import { useTranslation } from 'react-i18next'; + const NavigationBar = () => { + const [t] = useTranslation("global"); + const [setError] = useState(''); const { usernameGlobal, setUsernameGlobal } = useUser(); const navigate = useNavigate(); @@ -57,6 +61,8 @@ const NavigationBar = () => { } }; + + if (isHiddenRoute) { return null; // Si no estás en / o /App, no muestra la barra de navegación } @@ -88,33 +94,34 @@ const NavigationBar = () => { - - - - - - - - - + - - - - - - + + diff --git a/webapp/src/components/fragments/NavigationBar_Game.js b/webapp/src/components/fragments/NavigationBarGame.js similarity index 86% rename from webapp/src/components/fragments/NavigationBar_Game.js rename to webapp/src/components/fragments/NavigationBarGame.js index 702ec504..ec603922 100644 --- a/webapp/src/components/fragments/NavigationBar_Game.js +++ b/webapp/src/components/fragments/NavigationBarGame.js @@ -1,13 +1,16 @@ import React, {useCallback, useState} from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; -import { AppBar, Toolbar, IconButton, Menu, MenuItem, Grid, Button, Hidden} from '@mui/material'; +import { AppBar, Toolbar, IconButton, Menu, MenuItem, Grid, Button, Hidden, Snackbar} from '@mui/material'; import MenuIcon from '@mui/icons-material/Menu'; import axios from 'axios'; import Tooltip from '@mui/material/Tooltip'; +import { useTranslation } from 'react-i18next'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; const NavigationBar_Game = () => { + const [t] = useTranslation("global"); + const [error, setError] = useState(''); const navigate = useNavigate(); @@ -26,7 +29,6 @@ const NavigationBar_Game = () => { setAnchorEl(null); }; - const showHome = useCallback(async () => { try { axios.get(`${apiEndpoint}/restartGame`); @@ -35,7 +37,7 @@ const NavigationBar_Game = () => { console.log("Error: " + error.response.data.error); setError(error.response.data.error); } - }) + }, [navigate]); if (isHiddenRoute) { return null; // Si no estás en / o /App, no muestra la barra de navegación @@ -64,7 +66,7 @@ const NavigationBar_Game = () => { - + @@ -72,6 +74,11 @@ const NavigationBar_Game = () => { +
+ {error && ( + setError('')} message={`Error: ${error}`} /> + )} +
); }; diff --git a/webapp/src/components/images/esp.png b/webapp/src/components/images/esp.png new file mode 100644 index 00000000..e03bcf15 Binary files /dev/null and b/webapp/src/components/images/esp.png differ diff --git a/webapp/src/components/images/ing.png b/webapp/src/components/images/ing.png new file mode 100644 index 00000000..82f498b4 Binary files /dev/null and b/webapp/src/components/images/ing.png differ diff --git a/webapp/src/index.js b/webapp/src/index.js index 5e6f76e0..28c0d5ab 100644 --- a/webapp/src/index.js +++ b/webapp/src/index.js @@ -1,11 +1,15 @@ -import React from 'react'; + import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import NavigationBar from './components/fragments/NavigationBar'; -import NavigationBar_Game from './components/fragments/NavigationBar_Game'; +import NavigationBarGame from './components/fragments/NavigationBarGame'; import reportWebVitals from './reportWebVitals'; import { UserProvider } from './components/UserContext'; +import { I18nextProvider } from 'react-i18next'; +import i18next from 'i18next'; +import global_es from "./translations/es/global.json"; +import global_en from "./translations/en/global.json"; import { Route, @@ -25,13 +29,27 @@ import AllQuestions from './components/AllQuestions'; import Ranking from './components/Ranking'; import GameConfiguration from './components/GameConfiguration'; +i18next.init( { + interpolation: { escapevalue: false}, + lng:"es", + resources: { + es: { + global: global_es + }, + en: { + global: global_en + } + } +}) + const root = ReactDOM.createRoot(document.getElementById('root')); root.render( + - + }> }> @@ -49,6 +67,7 @@ root.render( + ); diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json new file mode 100644 index 00000000..27f8f113 --- /dev/null +++ b/webapp/src/translations/en/global.json @@ -0,0 +1,50 @@ +{ + "login": "Log in", + "usuario": "User", + "password": "Password", + "botonLogin": "ENTERS", + "enlaceLogin": "You do not have an account? Sign up here.", + "email": "Email", + "botonRegistro": "SIGN UP", + "registro": "SIGN UP", + "enlaceRegistro": "Do you already have an account? Sign in here.", + "textoInicio": "WELCOME TO WIQ", + "botonPartida": "NEW GAME", + "toolInicio":"Start", + "toolHistorico":"Historical", + "toolPerfil":"Profile", + "toolRanking":"Ranking", + "toolLogOut":"Log out", + "textoHistorico": "MY STATISTICS", + "textoPartJug": "Games played", + "textoPregResp": "Questions answered", + "textoAciertos": "Successes", + "textoFallos": "Failures", + "textoRatAc": "Success rate", + "textoTiempo": "Time played", + "textoTop": "Top 3 users", + "creado": "Created", + "textoPerfil": "PROFILE", + "textoPersonalizar": "Customize your game", + "textoNumPreg": "Number of questions", + "textoTiempoPreg": "Time available per question", + "textoTematicas": "Select the topics of the question to be able to play", + "tematicaTodas": "All", + "tematicaInf": "Computing", + "tematicaGeo": "Geography", + "tematicaCult": "Culture", + "tematicaPersonajes": "Characters", + "botonJugar": "PLAY", + "textoTiempoRest": "Time left: ", + "textoTiempoRest2": " seconds", + "mensajeLogin": "Successful login", + "textoAllUsers": "ALL USERS", + "textoAllQuestions": "ALL QUESTIONS", + "botonUsuarios": "USERS", + "botonPreguntas": "QUESTIONS", + "textoPregunta": "Question", + "textoRespuesta": "Answer", + "mensajeAddOk": "User added successfully", + "mensajeAddFail": "An user with that name has already registered", + "mensajeLogOut": "Closed session" +} \ No newline at end of file diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json new file mode 100644 index 00000000..2ff897b9 --- /dev/null +++ b/webapp/src/translations/es/global.json @@ -0,0 +1,51 @@ +{ + "login": "Inicia sesión", + "usuario": "Usuario", + "password": "Contraseña", + "botonLogin": "ENTRA", + "enlaceLogin": "¿No tienes cuenta? Registrate aquí.", + "email": "Email", + "botonRegistro": "REGÍSTRATE", + "registro": "REGÍSTRATE", + "enlaceRegistro": "¿Ya tienes cuenta? Inicia sesión aquí.", + "textoInicio": "¡BIENVENIDO A WIQ", + "botonPartida": "NUEVA PARTIDA", + "toolInicio":"Inicio", + "toolHistorico":"Historico", + "toolPerfil":"Perfil", + "toolRanking":"Ranking", + "toolLogOut":"Cerrar sesión", + "textoHistorico": "MIS ESTADÍSTICAS", + "textoPartJug": "Partidas jugadas", + "textoPregResp": "Preguntas respondidas", + "textoAciertos": "Aciertos", + "textoFallos": "Fallos", + "textoRatAc": "Ratio de acierto", + "textoTiempo": "Tiempo jugado", + "textoTop": "Top 3 usuarios", + "creado": "Creado", + "textoPerfil": "PERFIL", + "textoPersonalizar": "Personaliza tu partida", + "textoNumPreg": "Número de preguntas", + "textoTiempoPreg": "Tiempo disponible por pregunta", + "textoTematicas": "Selecciona las tematicas de la pregunta para poder jugar", + "tematicaTodas": "Todas", + "tematicaInf": "Informática", + "tematicaGeo": "Geografía", + "tematicaCult": "Cultura", + "tematicaPersonajes": "Personajes", + "botonJugar": "JUGAR", + "textoTiempoRest": "Tiempo restante: ", + "textoTiempoRest2": " segundos", + "mensajeLogin": "Inicio de sesión correcto", + "textoAllUsers": "TODOS LOS USUARIOS", + "textoAllQuestions": "TODOS LOS USUARIOS", + "botonUsuarios": "USUARIOS", + "botonPreguntas": "PREGUNTAS", + "textoPregunta": "Pregunta", + "textoRespuesta": "Respuesta", + "mensajeAddOk": "Usuario añadido correctamente", + "mensajeAddFail": "Ya se ha registrado un usuario con ese nombre", + "mensajeLogOut": "Sesion cerrada" +} + diff --git a/webapp/src/translations/i18n.js b/webapp/src/translations/i18n.js new file mode 100644 index 00000000..40bb6b8f --- /dev/null +++ b/webapp/src/translations/i18n.js @@ -0,0 +1,32 @@ +import eng from './en/global.json' +import esp from './es/global.json' +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; + +i18n + // detect user language + // learn more: https://github.com/i18next/i18next-browser-languageDetector + .use(LanguageDetector) + // pass the i18n instance to react-i18next. + .use(initReactI18next) + // init i18next + // for all options read: https://www.i18next.com/overview/configuration-options + .init({ + debug: true, + fallbackLng: 'en', + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + }, + resources: { + en: { + global: eng, + }, + es: { + global: esp, + } + } + }); + console.log(esp); + +export default i18n; \ No newline at end of file