diff --git a/services/client/index.html b/services/client/index.html
index e4b78ea..bd9ff54 100644
--- a/services/client/index.html
+++ b/services/client/index.html
@@ -1,13 +1,13 @@
-
+
-
-
-
-
- Vite + React + TS
-
-
-
-
-
+
+
+
+
+ Sintra
+
+
+
+
+
diff --git a/services/client/package.json b/services/client/package.json
index 93a3ffc..1691a1d 100644
--- a/services/client/package.json
+++ b/services/client/package.json
@@ -15,10 +15,12 @@
"@fontsource/roboto": "^5.0.8",
"@mui/material": "^5.14.18",
"firebase": "^10.6.0",
+ "i18next": "^23.7.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-firebase-hooks": "^5.1.1",
"react-hook-form": "^7.48.2",
+ "react-i18next": "^13.5.0",
"react-router-dom": "^6.19.0"
},
"devDependencies": {
diff --git a/services/client/public/flags/en.png b/services/client/public/flags/en.png
new file mode 100644
index 0000000..1f8c87e
Binary files /dev/null and b/services/client/public/flags/en.png differ
diff --git a/services/client/public/flags/es.png b/services/client/public/flags/es.png
new file mode 100644
index 0000000..60ba170
Binary files /dev/null and b/services/client/public/flags/es.png differ
diff --git a/services/client/public/letter-s.png b/services/client/public/letter-s.png
new file mode 100644
index 0000000..4be6a61
Binary files /dev/null and b/services/client/public/letter-s.png differ
diff --git a/services/client/src/_templates/Comp.tsx b/services/client/src/_templates/Comp.tsx
index 895a01c..00fdfea 100644
--- a/services/client/src/_templates/Comp.tsx
+++ b/services/client/src/_templates/Comp.tsx
@@ -1,9 +1,10 @@
+import React from 'react';
import { FC } from 'react';
-export const Comp: FC = () => {
+export const Comp: FC = React.memo(() => {
return (
<>
Comp Works.
>
);
-};
+});
diff --git a/services/client/src/_templates/CompWithChildren.tsx b/services/client/src/_templates/CompWithChildren.tsx
index 76b0588..5053e98 100644
--- a/services/client/src/_templates/CompWithChildren.tsx
+++ b/services/client/src/_templates/CompWithChildren.tsx
@@ -4,11 +4,11 @@ interface CompProps extends React.PropsWithChildren {
children: ReactElement | ReactElement[];
}
-export const Comp: FC = props => {
+export const Comp: FC = React.memo(props => {
return (
<>
Comp Works.
{props.children}
>
);
-};
+});
diff --git a/services/client/src/_templates/CompWithProps.tsx b/services/client/src/_templates/CompWithProps.tsx
index 4094e7f..636db23 100644
--- a/services/client/src/_templates/CompWithProps.tsx
+++ b/services/client/src/_templates/CompWithProps.tsx
@@ -2,10 +2,10 @@ import React, { FC } from 'react';
interface CompProps extends React.PropsWithChildren {}
-export const Comp: FC = () => {
+export const Comp: FC = React.memo(props => {
return (
<>
Comp Works.
>
);
-};
+});
diff --git a/services/client/src/core/translations/LanguagePicker.tsx b/services/client/src/core/translations/LanguagePicker.tsx
new file mode 100644
index 0000000..0b198e2
--- /dev/null
+++ b/services/client/src/core/translations/LanguagePicker.tsx
@@ -0,0 +1,28 @@
+import { ToggleButtonGroup, ToggleButton } from '@mui/material';
+import React, { ReactElement, FC } from 'react';
+import { useTranslation } from 'react-i18next';
+
+interface TProps extends React.PropsWithChildren {}
+
+const languages = ['es', 'en'];
+
+export const LanguagePicker: FC = React.memo(props => {
+ const { i18n } = useTranslation();
+
+ const [language, setLanguage] = React.useState(i18n.language);
+ const handleChange = (event: React.MouseEvent, newLanguage: string | null) => {
+ if (!newLanguage) return;
+ setLanguage(newLanguage);
+ i18n.changeLanguage(newLanguage);
+ };
+
+ return (
+
+ {languages.map((lang: string) => (
+
+
+
+ ))}
+
+ );
+});
diff --git a/services/client/src/core/translations/i18n.ts b/services/client/src/core/translations/i18n.ts
new file mode 100644
index 0000000..be1c0f1
--- /dev/null
+++ b/services/client/src/core/translations/i18n.ts
@@ -0,0 +1,31 @@
+import i18n from 'i18next';
+import { initReactI18next } from 'react-i18next';
+import es from './languages/es';
+import en from './languages/en';
+
+// the translations
+// (tip move them in a JSON file and import them,
+// or even better, manage them separated from your code: https://react.i18next.com/guides/multiple-translation-files)
+const resources = {
+ en: {
+ translation: en
+ },
+ es: {
+ translation: es
+ }
+};
+
+i18n
+ .use(initReactI18next) // passes i18n down to react-i18next
+ .init({
+ resources,
+ lng: 'es', // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
+ // you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
+ // if you're using a language detector, do not define the lng option
+
+ interpolation: {
+ escapeValue: false // react already safes from xss
+ }
+ });
+
+export default i18n;
diff --git a/services/client/src/core/translations/languages/en.ts b/services/client/src/core/translations/languages/en.ts
new file mode 100644
index 0000000..ff8b4c5
--- /dev/null
+++ b/services/client/src/core/translations/languages/en.ts
@@ -0,0 +1 @@
+export default {};
diff --git a/services/client/src/core/translations/languages/es.ts b/services/client/src/core/translations/languages/es.ts
new file mode 100644
index 0000000..ca66bf5
--- /dev/null
+++ b/services/client/src/core/translations/languages/es.ts
@@ -0,0 +1,9 @@
+// Spanish translations
+export default {
+ Login: 'Iniciar sesión',
+ Email: 'Correo electrónico',
+ Password: 'Contraseña',
+ 'Login with Google': 'Iniciar sesión con Google',
+ 'Register with Email': 'Registrarse con correo electrónico',
+ '© 2023 Sintra. All rights reserved.': '© 2023 Sintra. Todos los derechos reservados.'
+};
diff --git a/services/client/src/core/translations/useTranslate.tsx b/services/client/src/core/translations/useTranslate.tsx
new file mode 100644
index 0000000..7412f66
--- /dev/null
+++ b/services/client/src/core/translations/useTranslate.tsx
@@ -0,0 +1,6 @@
+import { useTranslation } from 'react-i18next';
+
+export const useTranslate = () => {
+ const { t } = useTranslation();
+ return t;
+};
diff --git a/services/client/src/main.tsx b/services/client/src/main.tsx
index fa6420c..322acf3 100644
--- a/services/client/src/main.tsx
+++ b/services/client/src/main.tsx
@@ -7,6 +7,8 @@ import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
+import './core/translations/i18n';
+
ReactDOM.createRoot(document.getElementById('root')!).render(
diff --git a/services/client/src/view/layout/Shell.tsx b/services/client/src/view/layout/Shell.tsx
index dfcb528..a18b8ee 100644
--- a/services/client/src/view/layout/Shell.tsx
+++ b/services/client/src/view/layout/Shell.tsx
@@ -2,6 +2,7 @@ import { Button, Popover } from '@mui/material';
import React, { ReactElement, FC } from 'react';
import { useLogout } from '../../core/firebase/firebase';
import { ProfileButton } from './ProfileButton';
+import { LanguagePicker } from '../../core/translations/LanguagePicker';
interface CompProps extends React.PropsWithChildren {
children: ReactElement | ReactElement[];
@@ -12,13 +13,14 @@ export const Shell: FC = props => {
-
+
+
Sintra
diff --git a/services/client/src/view/pages/LoginPage.tsx b/services/client/src/view/pages/LoginPage.tsx
index ee08081..43683d4 100644
--- a/services/client/src/view/pages/LoginPage.tsx
+++ b/services/client/src/view/pages/LoginPage.tsx
@@ -3,6 +3,8 @@ import { FC, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useSignInGoogle, useSignInPassword } from '../../core/firebase/firebase';
import { useForm, SubmitHandler } from 'react-hook-form';
+import { useTranslate } from '../../core/translations/useTranslate';
+import { LanguagePicker } from '../../core/translations/LanguagePicker';
type LoginFormFields = {
email: string;
@@ -10,7 +12,9 @@ type LoginFormFields = {
password2: string;
};
-export const LoginPage: FC = () => {
+export const LoginPage: FC = props => {
+ const t = useTranslate();
+
const navigate = useNavigate();
const [
signInWithEmailAndPassword,
@@ -36,13 +40,16 @@ export const LoginPage: FC = () => {
return (
-
+
+
+
+
Sintra
@@ -52,7 +59,7 @@ export const LoginPage: FC = () => {
-
© 2023 Sintra. All rights reserved.
+
{t('© 2023 Sintra. All rights reserved.')}
);
diff --git a/services/client/src/view/pages/RegisterPage.tsx b/services/client/src/view/pages/RegisterPage.tsx
index ef4f2d2..3606dfd 100644
--- a/services/client/src/view/pages/RegisterPage.tsx
+++ b/services/client/src/view/pages/RegisterPage.tsx
@@ -3,6 +3,8 @@ import { FC, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { SubmitHandler, useForm } from 'react-hook-form';
import { useCreateUser } from '../../core/firebase/firebase';
+import { LanguagePicker } from '../../core/translations/LanguagePicker';
+import { useTranslate } from '../../core/translations/useTranslate';
type RegisterFormFields = {
email: string;
@@ -11,6 +13,7 @@ type RegisterFormFields = {
};
export const RegisterPage: FC = () => {
+ const t = useTranslate();
const navigate = useNavigate();
const [
createUserWithEmailAndPassword,
@@ -34,12 +37,15 @@ export const RegisterPage: FC = () => {
return (