{user ? (
- Welcome {user.name}
+ {t('welcome', { ns: 'common' })}{' '}
+ {user.name}
) : (
diff --git a/src/pages/LandingPage/LandingPage.tsx b/src/pages/LandingPage/LandingPage.tsx
index 3d04be6..ef4360e 100644
--- a/src/pages/LandingPage/LandingPage.tsx
+++ b/src/pages/LandingPage/LandingPage.tsx
@@ -1,4 +1,5 @@
import { Navigate } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
import { useAuth } from 'hooks/useAuth';
import Page from 'components/Page/Page';
@@ -12,6 +13,7 @@ import Page from 'components/Page/Page';
* @returns {JSX.Element} JSX
*/
const LandingPage = (): JSX.Element => {
+ const { t } = useTranslation();
const authContext = useAuth();
if (authContext.isAuthenticated) {
@@ -21,9 +23,11 @@ const LandingPage = (): JSX.Element => {
return (
-
Let's get started
+
+ {t('letsGetStarted', { ns: 'common' })}
+
-
Creating React apps just got a lot simpler
+
{t('creatingReactApps', { ns: 'common' })}
);
diff --git a/src/pages/UsersPage/components/UserTasksCard.tsx b/src/pages/UsersPage/components/UserTasksCard.tsx
index e6eee0e..c83edb8 100644
--- a/src/pages/UsersPage/components/UserTasksCard.tsx
+++ b/src/pages/UsersPage/components/UserTasksCard.tsx
@@ -2,6 +2,7 @@ import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import filter from 'lodash/filter';
import classNames from 'classnames';
+import { useTranslation } from 'react-i18next';
import { useGetUserTasks } from '../api/useGetUserTasks';
import Card, { CardProps } from 'components/Card/Card';
@@ -31,20 +32,21 @@ const UserTasksCard = ({
...props
}: UserTasksCardProps): JSX.Element => {
const navigate = useNavigate();
+ const { t } = useTranslation();
const { data: tasks, error, isLoading } = useGetUserTasks({ userId });
const incompleteTasks = filter(tasks, { completed: false });
const tasksMessage = useMemo(() => {
if (error) {
- return 'A problem occurred fetching your tasks.';
+ return t('task.errors.fetchingList', { ns: 'users' });
}
if (incompleteTasks.length === 0) {
- return 'You are all caught up!';
+ return t('task.allComplete', { ns: 'users' });
}
- return `You have ${incompleteTasks.length} tasks to complete.`;
- }, [error, incompleteTasks]);
+ return t('task.toComplete', { ns: 'users', val: incompleteTasks.length });
+ }, [error, incompleteTasks, t]);
return (
navigate(`/app/users/${userId}/tasks`)} data-testid={testId}>
@@ -63,7 +65,7 @@ const UserTasksCard = ({
) : (
-
Tasks
+
{t('task.tasks', { ns: 'users' })}
{tasksMessage}
)}
diff --git a/src/pages/UsersPage/components/__tests__/UserTasksCard.test.tsx b/src/pages/UsersPage/components/__tests__/UserTasksCard.test.tsx
index d92e856..672ec44 100644
--- a/src/pages/UsersPage/components/__tests__/UserTasksCard.test.tsx
+++ b/src/pages/UsersPage/components/__tests__/UserTasksCard.test.tsx
@@ -1,12 +1,12 @@
import { render, screen } from 'test/test-utils';
import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { UseQueryResult } from '@tanstack/react-query';
+import userEvent from '@testing-library/user-event';
import * as UseGetUserTasks from '../../api/useGetUserTasks';
+import { todosFixture } from '__fixtures__/todos';
import UserTasksCard from '../UserTasksCard';
-import { todosFixture } from '__fixtures__/todos';
-import { UseQueryResult } from '@tanstack/react-query';
-import userEvent from '@testing-library/user-event';
// mock select functions from react-router-dom
const mockNavigate = vi.fn();
@@ -96,7 +96,7 @@ describe('UserTasksCard', () => {
// ASSERT
expect(screen.getByTestId('card-user-tasks-message').textContent).toBe(
- 'You are all caught up!',
+ 'All your tasks are complete!',
);
});
diff --git a/src/test/test-utils.tsx b/src/test/test-utils.tsx
index a3936b0..1b1e547 100644
--- a/src/test/test-utils.tsx
+++ b/src/test/test-utils.tsx
@@ -6,6 +6,8 @@ import {
RenderHookOptions,
RenderOptions,
} from '@testing-library/react';
+
+import 'utils/i18n';
import WithAllProviders from './WithAllProviders';
const customRender = (ui: React.ReactElement, options?: RenderOptions, { route = '/' } = {}) => {
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index f7d8abf..a81489c 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -14,6 +14,7 @@ export enum QueryKeys {
* Keys used for browser local storage.
*/
export enum StorageKeys {
+ Language = 'react-starter.language',
Settings = 'react-starter.settings',
User = 'react-starter.user',
UserTokens = 'react-starter.user-tokens',
diff --git a/src/utils/i18n/__tests__/i18n.test.ts b/src/utils/i18n/__tests__/i18n.test.ts
new file mode 100644
index 0000000..d031303
--- /dev/null
+++ b/src/utils/i18n/__tests__/i18n.test.ts
@@ -0,0 +1,10 @@
+import { describe, expect, it } from 'vitest';
+
+import i18n from 'utils/i18n';
+
+describe('i18n', () => {
+ it('should initialize i18n', () => {
+ // ASSERT
+ expect(i18n).toBeDefined();
+ });
+});
diff --git a/src/utils/i18n/index.ts b/src/utils/i18n/index.ts
new file mode 100644
index 0000000..ef16402
--- /dev/null
+++ b/src/utils/i18n/index.ts
@@ -0,0 +1,41 @@
+import i18n from 'i18next';
+import { initReactI18next } from 'react-i18next';
+import LanguageDetector from 'i18next-browser-languagedetector';
+
+import { StorageKeys } from 'utils/constants';
+
+// translation resources
+import en from './locales/en';
+import es from './locales/es';
+import fr from './locales/fr';
+
+i18n
+ .use(LanguageDetector)
+ .use(initReactI18next)
+ .init({
+ // logging
+ debug: true,
+
+ // languages, namespaces, and resources
+ supportedLngs: ['en', 'es', 'fr'],
+ fallbackLng: 'en',
+ ns: ['common', 'users'],
+ defaultNS: 'common',
+ resources: {
+ en,
+ es,
+ fr,
+ },
+
+ // translation defaults
+ interpolation: {
+ escapeValue: false,
+ },
+
+ // plugin - language detector
+ detection: {
+ lookupLocalStorage: StorageKeys.Language,
+ },
+ });
+
+export default i18n;
diff --git a/src/utils/i18n/locales/en/common.json b/src/utils/i18n/locales/en/common.json
new file mode 100644
index 0000000..0f1343d
--- /dev/null
+++ b/src/utils/i18n/locales/en/common.json
@@ -0,0 +1,9 @@
+{
+ "creatingReactApps": "Creating React apps just got a lot simpler",
+ "letsGetStarted": "Let's get started",
+ "privacy": "Privacy",
+ "privacyPolicy": "Privacy policy",
+ "terms": "Terms",
+ "termsAndConditions": "Terms and conditions",
+ "welcome": "Welcome"
+}
diff --git a/src/utils/i18n/locales/en/index.ts b/src/utils/i18n/locales/en/index.ts
new file mode 100644
index 0000000..d762461
--- /dev/null
+++ b/src/utils/i18n/locales/en/index.ts
@@ -0,0 +1,4 @@
+import common from './common.json';
+import users from './users.json';
+
+export default { common, users };
diff --git a/src/utils/i18n/locales/en/users.json b/src/utils/i18n/locales/en/users.json
new file mode 100644
index 0000000..4910b8c
--- /dev/null
+++ b/src/utils/i18n/locales/en/users.json
@@ -0,0 +1,11 @@
+{
+ "user": {},
+ "task": {
+ "allComplete": "All your tasks are complete!",
+ "errors": {
+ "fetchingList": "A problem occurred fetching your tasks."
+ },
+ "tasks": "tasks",
+ "toComplete": "You have {{val}} tasks to complete."
+ }
+}
diff --git a/src/utils/i18n/locales/es/common.json b/src/utils/i18n/locales/es/common.json
new file mode 100644
index 0000000..fe2e9a4
--- /dev/null
+++ b/src/utils/i18n/locales/es/common.json
@@ -0,0 +1,9 @@
+{
+ "creatingReactApps": "Crear aplicaciones React ahora es mucho más sencillo",
+ "letsGetStarted": "Empecemos",
+ "privacy": "Privacidad",
+ "privacyPolicy": "Política de privacidad",
+ "terms": "Términos",
+ "termsAndConditions": "Términos y condiciones",
+ "welcome": "Bienvenido"
+}
diff --git a/src/utils/i18n/locales/es/index.ts b/src/utils/i18n/locales/es/index.ts
new file mode 100644
index 0000000..d762461
--- /dev/null
+++ b/src/utils/i18n/locales/es/index.ts
@@ -0,0 +1,4 @@
+import common from './common.json';
+import users from './users.json';
+
+export default { common, users };
diff --git a/src/utils/i18n/locales/es/users.json b/src/utils/i18n/locales/es/users.json
new file mode 100644
index 0000000..578dbf3
--- /dev/null
+++ b/src/utils/i18n/locales/es/users.json
@@ -0,0 +1,11 @@
+{
+ "user": {},
+ "task": {
+ "allComplete": "¡Todas tus tareas están completas!",
+ "errors": {
+ "fetchingList": "Ocurrió un problema al recuperar tus tareas."
+ },
+ "tasks": "tareas",
+ "toComplete": "Tienes {{val}} tarea para completar."
+ }
+}
diff --git a/src/utils/i18n/locales/fr/common.json b/src/utils/i18n/locales/fr/common.json
new file mode 100644
index 0000000..4c73302
--- /dev/null
+++ b/src/utils/i18n/locales/fr/common.json
@@ -0,0 +1,9 @@
+{
+ "creatingReactApps": "La création d'applications React est devenue beaucoup plus simple",
+ "letsGetStarted": "Commençons",
+ "privacy": "Confidentialité",
+ "privacyPolicy": "Politique de confidentialité",
+ "terms": "Termes",
+ "termsAndConditions": "Termes et conditions",
+ "welcome": "Bienvenue"
+}
diff --git a/src/utils/i18n/locales/fr/index.ts b/src/utils/i18n/locales/fr/index.ts
new file mode 100644
index 0000000..d762461
--- /dev/null
+++ b/src/utils/i18n/locales/fr/index.ts
@@ -0,0 +1,4 @@
+import common from './common.json';
+import users from './users.json';
+
+export default { common, users };
diff --git a/src/utils/i18n/locales/fr/users.json b/src/utils/i18n/locales/fr/users.json
new file mode 100644
index 0000000..27e0541
--- /dev/null
+++ b/src/utils/i18n/locales/fr/users.json
@@ -0,0 +1,11 @@
+{
+ "user": {},
+ "task": {
+ "allComplete": "Toutes vos tâches sont terminées!",
+ "errors": {
+ "fetchingList": "Un problème est survenu lors de la récupération de vos tâches."
+ },
+ "tasks": "tâches",
+ "toComplete": "Vous avez {{val}} tâche à accomplir."
+ }
+}