diff --git a/frontend/apps/mobile/src/i18n.ts b/frontend/apps/mobile/src/i18n.ts new file mode 100644 index 00000000..55b49bbe --- /dev/null +++ b/frontend/apps/mobile/src/i18n.ts @@ -0,0 +1,15 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import { resources, defaultNS } from "@abrechnung/translations"; + +i18n.use(initReactI18next).init({ + ns: [defaultNS], + resources, + defaultNS, + lng: "en", + fallbackLng: "en", + debug: true, + interpolation: { escapeValue: false }, +}); + +export default i18n; diff --git a/frontend/apps/mobile/src/i18next.d.ts b/frontend/apps/mobile/src/i18next.d.ts new file mode 100644 index 00000000..8e2672a6 --- /dev/null +++ b/frontend/apps/mobile/src/i18next.d.ts @@ -0,0 +1,9 @@ +import "i18next"; +import type { resources, defaultNS } from "@abrechnung/translations"; + +declare module "i18next" { + interface CustomTypeOptions { + defaultNS: typeof defaultNS; + resources: (typeof resources)["en"]; + } +} diff --git a/frontend/apps/web/src/app/authenticated-layout/AuthenticatedLayout.tsx b/frontend/apps/web/src/app/authenticated-layout/AuthenticatedLayout.tsx index 993c3c73..2900f28b 100644 --- a/frontend/apps/web/src/app/authenticated-layout/AuthenticatedLayout.tsx +++ b/frontend/apps/web/src/app/authenticated-layout/AuthenticatedLayout.tsx @@ -45,6 +45,7 @@ import { useTheme } from "@mui/material/styles"; import { Banner } from "../../components/style/Banner"; import Loading from "../../components/style/Loading"; import styles from "./AuthenticatedLayout.module.css"; +import { LanguageSelect } from "@/components/LanguageSelect"; const drawerWidth = 240; const AUTH_FALLBACK = "/login"; @@ -216,6 +217,7 @@ export const AuthenticatedLayout: React.FC = () => {
+ = ({ activeGroupId }) => { + const { t } = useTranslation(); const isGuest = useAppSelector((state) => selectIsGuestUser({ state: selectAuthSlice(state) })); const groups = useAppSelector((state) => selectGroups({ state: selectGroupSlice(state) })); const [showGroupCreationModal, setShowGroupCreationModal] = useState(false); @@ -45,9 +47,11 @@ export const SidebarGroupList: React.FC = ({ activeGroupId }) => { {!isGuest && ( - - - + + + + + )} diff --git a/frontend/apps/web/src/app/unauthenticated-layout/UnauthenticatedLayout.tsx b/frontend/apps/web/src/app/unauthenticated-layout/UnauthenticatedLayout.tsx index 9617ccb1..41d829ad 100644 --- a/frontend/apps/web/src/app/unauthenticated-layout/UnauthenticatedLayout.tsx +++ b/frontend/apps/web/src/app/unauthenticated-layout/UnauthenticatedLayout.tsx @@ -4,6 +4,7 @@ import { AppBar, Box, Button, Container, CssBaseline, Toolbar, Typography } from import { Banner } from "../../components/style/Banner"; import { selectIsAuthenticated } from "@abrechnung/redux"; import { useAppSelector, selectAuthSlice } from "../../store"; +import { LanguageSelect } from "@/components/LanguageSelect"; export const UnauthenticatedLayout: React.FC = () => { const authenticated = useAppSelector((state) => selectIsAuthenticated({ state: selectAuthSlice(state) })); @@ -27,6 +28,7 @@ export const UnauthenticatedLayout: React.FC = () => { Abrechnung + diff --git a/frontend/apps/web/src/components/LanguageSelect.tsx b/frontend/apps/web/src/components/LanguageSelect.tsx new file mode 100644 index 00000000..73c8e596 --- /dev/null +++ b/frontend/apps/web/src/components/LanguageSelect.tsx @@ -0,0 +1,21 @@ +import { MenuItem, Select, SelectChangeEvent, SelectProps } from "@mui/material"; +import * as React from "react"; +import { useTranslation } from "react-i18next"; + +export type LanguageSelectProps = Omit; + +export const LanguageSelect: React.FC = (props) => { + const { t, i18n } = useTranslation(); + + const handleSetLanguage = (event: SelectChangeEvent) => { + const lang = event.target.value as string; + i18n.changeLanguage(lang); + }; + + return ( + + ); +}; diff --git a/frontend/apps/web/src/i18n.ts b/frontend/apps/web/src/i18n.ts index 145e77a2..0949cd7d 100644 --- a/frontend/apps/web/src/i18n.ts +++ b/frontend/apps/web/src/i18n.ts @@ -1,14 +1,15 @@ import i18n from "i18next"; import { initReactI18next } from "react-i18next"; -import Backend from "i18next-http-backend"; import LanguageDetector from "i18next-browser-languagedetector"; +import { resources, defaultNS } from "@abrechnung/translations"; -i18n.use(Backend) - .use(LanguageDetector) +i18n.use(LanguageDetector) .use(initReactI18next) .init({ - backend: { loadPath: "/assets/locales/{{lng}}/{{ns}}.json" }, - lng: "en", + ns: [defaultNS], + resources, + defaultNS, + lng: "en-US", fallbackLng: "en", debug: true, interpolation: { escapeValue: false }, diff --git a/frontend/apps/web/src/i18next.d.ts b/frontend/apps/web/src/i18next.d.ts new file mode 100644 index 00000000..0d806f61 --- /dev/null +++ b/frontend/apps/web/src/i18next.d.ts @@ -0,0 +1,10 @@ +import "i18next"; +import type { resources, defaultNS } from "@abrechnung/translations"; + +declare module "i18next" { + interface CustomTypeOptions { + defaultNS: typeof defaultNS; + nsSeparator: ""; + resources: { "": (typeof resources)["en"]["translations"] }; + } +} diff --git a/frontend/apps/web/src/pages/accounts/Balances.tsx b/frontend/apps/web/src/pages/accounts/Balances.tsx index 6c61ea5c..9bc78528 100644 --- a/frontend/apps/web/src/pages/accounts/Balances.tsx +++ b/frontend/apps/web/src/pages/accounts/Balances.tsx @@ -152,7 +152,7 @@ export const Balances: React.FC = ({ groupId }) => { top: 20, right: 20, bottom: 20, - left: 20, + left: 30, }} layout="vertical" onClick={handleBarClick} diff --git a/frontend/apps/web/src/pages/profile/ChangeEmail.tsx b/frontend/apps/web/src/pages/profile/ChangeEmail.tsx index 52f54e4e..f959e7b2 100644 --- a/frontend/apps/web/src/pages/profile/ChangeEmail.tsx +++ b/frontend/apps/web/src/pages/profile/ChangeEmail.tsx @@ -17,14 +17,14 @@ type FormSchema = z.infer; export const ChangeEmail: React.FC = () => { const { t } = useTranslation(); - useTitle(t("Abrechnung - Change E-Mail")); + useTitle(t("profile.changeEmail.tabTitle")); const handleSubmit = (values: FormSchema, { setSubmitting, resetForm }: FormikHelpers) => { api.client.auth .changeEmail({ requestBody: { password: values.password, email: values.newEmail } }) .then(() => { setSubmitting(false); - toast.success(t("Requested email change, you should receive an email with a confirmation link soon")); + toast.success(t("profile.changeEmail.success")); resetForm(); }) .catch((error) => { @@ -36,7 +36,7 @@ export const ChangeEmail: React.FC = () => { return ( - {t("Change E-Mail")} + {t("profile.changeEmail.pageTitle")} { value={values.password} onChange={handleChange} onBlur={handleBlur} - label={t("Password")} + label={t("common.password")} error={touched.password && !!errors.password} helperText={touched.password && errors.password} /> @@ -69,14 +69,14 @@ export const ChangeEmail: React.FC = () => { value={values.newEmail} onChange={handleChange} onBlur={handleBlur} - label={t("New E-Mail")} + label={t("profile.changeEmail.newEmail")} error={touched.newEmail && !!errors.newEmail} helperText={touched.newEmail && errors.newEmail} /> {isSubmitting && } )} diff --git a/frontend/apps/web/src/pages/profile/ChangePassword.tsx b/frontend/apps/web/src/pages/profile/ChangePassword.tsx index 9267d82b..b10280ee 100644 --- a/frontend/apps/web/src/pages/profile/ChangePassword.tsx +++ b/frontend/apps/web/src/pages/profile/ChangePassword.tsx @@ -23,14 +23,14 @@ type FormSchema = z.infer; export const ChangePassword: React.FC = () => { const { t } = useTranslation(); - useTitle(t("Abrechnung - Change Password")); + useTitle(t("profile.changePassword.tabTitle")); const handleSubmit = (values: FormSchema, { setSubmitting, resetForm }: FormikHelpers) => { api.client.auth .changePassword({ requestBody: { old_password: values.password, new_password: values.newPassword } }) .then(() => { setSubmitting(false); - toast.success(t("Successfully changed password")); + toast.success(t("profile.changePassword.success")); resetForm(); }) .catch((error) => { @@ -42,7 +42,7 @@ export const ChangePassword: React.FC = () => { return ( - {t("Change Password")} + {t("profile.changePassword.pageTitle")} { margin="normal" type="password" name="password" - label={t("Password")} + label={t("common.password")} variant="standard" value={values.password} onChange={handleChange} @@ -75,7 +75,7 @@ export const ChangePassword: React.FC = () => { margin="normal" type="password" name="newPassword" - label={t("New Password")} + label={t("profile.changePassword.newPassword")} variant="standard" value={values.newPassword} onChange={handleChange} @@ -93,14 +93,14 @@ export const ChangePassword: React.FC = () => { margin="normal" type="password" name="newPassword2" - label={t("Repeat Password")} + label={t("profile.changePassword.repeatPassword")} error={touched.newPassword2 && !!errors.newPassword2} helperText={touched.newPassword2 && errors.newPassword2} /> {isSubmitting && } )} diff --git a/frontend/apps/web/src/pages/profile/Profile.tsx b/frontend/apps/web/src/pages/profile/Profile.tsx index 7e0d01e0..102be4a3 100644 --- a/frontend/apps/web/src/pages/profile/Profile.tsx +++ b/frontend/apps/web/src/pages/profile/Profile.tsx @@ -5,37 +5,34 @@ import { selectAuthSlice, useAppSelector } from "@/store"; import { selectProfile } from "@abrechnung/redux"; import { Alert, List, ListItem, ListItemText, Typography } from "@mui/material"; import { DateTime } from "luxon"; -import React from "react"; +import * as React from "react"; +import { useTranslation } from "react-i18next"; export const Profile: React.FC = () => { + const { t } = useTranslation(); const profile = useAppSelector((state) => selectProfile({ state: selectAuthSlice(state) })); - useTitle("Abrechnung - Profile"); + useTitle(t("profile.index.tabTitle")); return ( - Profile + {t("profile.index.pageTitle")} {profile === undefined ? ( ) : ( <> - {profile.is_guest_user && ( - - You are a guest user on this Abrechnung and therefore not permitted to create new groups or - group invites. - - )} + {profile.is_guest_user && {t("profile.index.guestUserDisclaimer")}} - + - + { ); }; - -export default Profile; diff --git a/frontend/apps/web/src/pages/profile/Settings.tsx b/frontend/apps/web/src/pages/profile/Settings.tsx index 224c9e6b..8a508858 100644 --- a/frontend/apps/web/src/pages/profile/Settings.tsx +++ b/frontend/apps/web/src/pages/profile/Settings.tsx @@ -1,4 +1,14 @@ -import React, { useState } from "react"; +import { MobilePaper } from "@/components/style/mobile"; +import { useTitle } from "@/core/utils"; +import { + ThemeMode, + persistor, + selectSettingsSlice, + selectTheme, + themeChanged, + useAppDispatch, + useAppSelector, +} from "@/store"; import { Alert, Box, @@ -8,32 +18,24 @@ import { DialogContent, DialogContentText, DialogTitle, - Divider, FormControl, FormGroup, FormLabel, MenuItem, Select, + Stack, Typography, } from "@mui/material"; -import { MobilePaper } from "../../components/style/mobile"; -import { useTitle } from "../../core/utils"; -import { - useAppDispatch, - useAppSelector, - themeChanged, - selectTheme, - persistor, - ThemeMode, - selectSettingsSlice, -} from "../../store"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import { toast } from "react-toastify"; export const Settings: React.FC = () => { + const { t } = useTranslation(); const dispatch = useAppDispatch(); const themeMode = useAppSelector((state) => selectTheme({ state: selectSettingsSlice(state) })); - useTitle("Abrechnung - Settings"); + useTitle(t("profile.settings.tabTitle")); const [confirmClearCacheOpen, setConfirmClearCacheOpen] = useState(false); @@ -62,44 +64,36 @@ export const Settings: React.FC = () => { return ( - - Settings - - - These settings are stored locally on your device. Clearing your Browser's local storage will reset - them. - - - - - Theme - - - - - - - - Clear Cache - - - - {/*ACHTUNG!*/} - - + + + {t("profile.settings.pageTitle")} + + {t("profile.settings.info")} + + + + {t("profile.settings.theme")} + + + + + + + + { {"Clear Cache?"} - This action will clear your local cache. All your settings (this page) will not be reset. + {t("profile.settings.confirmClearCache")} - + diff --git a/frontend/apps/web/tsconfig.json b/frontend/apps/web/tsconfig.json index 576a22f9..988dd790 100644 --- a/frontend/apps/web/tsconfig.json +++ b/frontend/apps/web/tsconfig.json @@ -19,6 +19,7 @@ "@abrechnung/redux": ["../../libs/redux/src/index.ts"], "@abrechnung/types": ["../../libs/types/src/index.ts"], "@abrechnung/utils": ["../../libs/utils/src/index.ts"], + "@abrechnung/translations": ["../../libs/translations/src/index.ts"], "@/*": ["src/*"] } }, diff --git a/frontend/assets/locales/de/translation.json b/frontend/assets/locales/de/translation.json deleted file mode 100644 index 9986efd7..00000000 --- a/frontend/assets/locales/de/translation.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Abrechnung - Change E-Mail": "Abrechnung - E-Mail ändern", - "Abrechnung - Change Password": "Abrechnung - Passwort ändern", - "Successfully changed password": "Passwort erfolgreich geändert", - "Change Password": "Passwort Ändern", - "Password": "Passwort", - "New Password": "Neues Passwort", - "Repeat Password": "Passwort wiederholen", - "Save": "Speichern" -} diff --git a/frontend/libs/translations/.eslintrc.json b/frontend/libs/translations/.eslintrc.json new file mode 100644 index 00000000..5626944b --- /dev/null +++ b/frontend/libs/translations/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/frontend/libs/translations/README.md b/frontend/libs/translations/README.md new file mode 100644 index 00000000..f8d3a2c1 --- /dev/null +++ b/frontend/libs/translations/README.md @@ -0,0 +1,3 @@ +# translations + +This library was generated with [Nx](https://nx.dev). diff --git a/frontend/libs/translations/project.json b/frontend/libs/translations/project.json new file mode 100644 index 00000000..c99f01f2 --- /dev/null +++ b/frontend/libs/translations/project.json @@ -0,0 +1,16 @@ +{ + "name": "translations", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/translations/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/translations/**/*.ts"] + } + } + }, + "tags": [] +} diff --git a/frontend/libs/translations/src/index.ts b/frontend/libs/translations/src/index.ts new file mode 100644 index 00000000..8cd5167d --- /dev/null +++ b/frontend/libs/translations/src/index.ts @@ -0,0 +1 @@ +export * from "./lib"; diff --git a/frontend/libs/translations/src/lib/de.ts b/frontend/libs/translations/src/lib/de.ts new file mode 100644 index 00000000..8b34f60b --- /dev/null +++ b/frontend/libs/translations/src/lib/de.ts @@ -0,0 +1,12 @@ +import type { en } from "./en"; + +const translations = { + languages: { + en: "Englisch", + de: "Deutsch", + }, +} satisfies Partial<(typeof en)["translations"]>; + +export const de = { + translations, +}; diff --git a/frontend/libs/translations/src/lib/en.ts b/frontend/libs/translations/src/lib/en.ts new file mode 100644 index 00000000..aaae5360 --- /dev/null +++ b/frontend/libs/translations/src/lib/en.ts @@ -0,0 +1,55 @@ +const translations = { + common: { + username: "Username", + email: "E-Mail", + password: "Password", + save: "Save", + yes: "Yes", + cancel: "Cancel", + }, + groups: { + addGroup: "Add Group", + }, + profile: { + index: { + tabTitle: "Abrechnung - Profile", + pageTitle: "Profile", + guestUserDisclaimer: + "You are a guest user on this Abrechnung and therefore not permitted to create new groups or group invites.", + registered: "Registered", + }, + settings: { + tabTitle: "Abrechnung - Settings", + pageTitle: "Settings", + info: "These settings are stored locally on your device. Clearing your Browser's local storage will reset them.", + theme: "Theme", + themeSystemDefault: "System Default", + themeDarkMode: "Dark Mode", + themeLightMode: "Light Mode", + clearCache: "Clear Cache", + confirmClearCache: + "This action will clear your local cache. All your settings (this page) will not be reset.", + }, + changePassword: { + tabTitle: "Abrechnung - Change Password", + pageTitle: "Change Password", + success: "Successfully changed password", + newPassword: "New Password", + repeatPassword: "Repeat Password", + }, + changeEmail: { + tabTitle: "Abrechnung - Change E-Mail", + pageTitle: "Change E-Mail", + success: "Requested email change, you should receive an email with a confirmation link soon", + newEmail: "New E-Mail", + }, + }, + languages: { + en: "English", + de: "German", + }, +}; + +export const en = { + translations, +}; diff --git a/frontend/libs/translations/src/lib/index.ts b/frontend/libs/translations/src/lib/index.ts new file mode 100644 index 00000000..690c2092 --- /dev/null +++ b/frontend/libs/translations/src/lib/index.ts @@ -0,0 +1,11 @@ +import { en } from "./en"; +import { de } from "./de"; +export * from "./de"; +export * from "./en"; + +export const defaultNS = "translations"; + +export const resources = { + en: en, + de: de, +}; diff --git a/frontend/libs/translations/tsconfig.json b/frontend/libs/translations/tsconfig.json new file mode 100644 index 00000000..4d6eaab3 --- /dev/null +++ b/frontend/libs/translations/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs" + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/frontend/libs/translations/tsconfig.lib.json b/frontend/libs/translations/tsconfig.lib.json new file mode 100644 index 00000000..d349f641 --- /dev/null +++ b/frontend/libs/translations/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 23d02166..58715512 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -36,16 +36,15 @@ "core-js": "^3.33.2", "deepmerge": "^4.3.1", "formik": "^2.4.5", - "i18next": "^23.6.0", + "i18next": "^23.7.13", "i18next-browser-languagedetector": "^7.1.0", - "i18next-http-backend": "^2.3.1", "localforage": "^1.10.0", "luxon": "^3.4.3", "multer": "^1.4.5-lts.1", "proxy-memoize": "^1.2.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-i18next": "^13.3.1", + "react-i18next": "^13.5.0", "react-native": "0.72.6", "react-native-gesture-handler": "~2.13.4", "react-native-pager-view": "^6.2.2", @@ -23817,9 +23816,9 @@ "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" }, "node_modules/i18next": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.6.0.tgz", - "integrity": "sha512-z0Cxr0MGkt+kli306WS4nNNM++9cgt2b2VCMprY92j+AIab/oclgPxdwtTZVLP1zn5t5uo8M6uLsZmYrcjr3HA==", + "version": "23.7.13", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.7.13.tgz", + "integrity": "sha512-DbCPlw6VmURSZa43iOnycxq9o15e+WuBWDBZ3aj+gQZcDz4sgnuKwrcwmP1n8gSSCwCN7CRFGTpnwTd93A16Mg==", "funding": [ { "type": "individual", @@ -23835,7 +23834,7 @@ } ], "dependencies": { - "@babel/runtime": "^7.22.5" + "@babel/runtime": "^7.23.2" } }, "node_modules/i18next-browser-languagedetector": { @@ -23846,60 +23845,6 @@ "@babel/runtime": "^7.19.4" } }, - "node_modules/i18next-http-backend": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.3.1.tgz", - "integrity": "sha512-jnagFs5cnq4ryb+g92Hex4tB5kj3tWmiRWx8gHMCcE/PEgV1fjH5rC7xyJmPSgyb9r2xgcP8rvZxPKgsmvMqTw==", - "dependencies": { - "cross-fetch": "4.0.0" - } - }, - "node_modules/i18next-http-backend/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "node_modules/i18next-http-backend/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/i18next-http-backend/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/i18next-http-backend/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/i18next-http-backend/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/i18next-parser": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/i18next-parser/-/i18next-parser-8.9.0.tgz", @@ -32273,9 +32218,9 @@ } }, "node_modules/react-i18next": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.3.1.tgz", - "integrity": "sha512-JAtYREK879JXaN9GdzfBI4yJeo/XyLeXWUsRABvYXiFUakhZJ40l+kaTo+i+A/3cKIED41kS/HAbZ5BzFtq/Og==", + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.5.0.tgz", + "integrity": "sha512-CFJ5NDGJ2MUyBohEHxljOq/39NQ972rh1ajnadG9BjTk+UXbHLq4z5DKEbEQBDoIhUmmbuS/fIMJKo6VOax1HA==", "dependencies": { "@babel/runtime": "^7.22.5", "html-parse-stringify": "^3.0.1" diff --git a/frontend/package.json b/frontend/package.json index d1d7ca0b..24b35c12 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,16 +36,15 @@ "core-js": "^3.33.2", "deepmerge": "^4.3.1", "formik": "^2.4.5", - "i18next": "^23.6.0", + "i18next": "^23.7.13", "i18next-browser-languagedetector": "^7.1.0", - "i18next-http-backend": "^2.3.1", "localforage": "^1.10.0", "luxon": "^3.4.3", "multer": "^1.4.5-lts.1", "proxy-memoize": "^1.2.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-i18next": "^13.3.1", + "react-i18next": "^13.5.0", "react-native": "0.72.6", "react-native-gesture-handler": "~2.13.4", "react-native-pager-view": "^6.2.2", diff --git a/frontend/tsconfig.base.json b/frontend/tsconfig.base.json index 01a3457f..6d8f76ed 100644 --- a/frontend/tsconfig.base.json +++ b/frontend/tsconfig.base.json @@ -18,6 +18,7 @@ "@abrechnung/api": ["libs/api/src/index.ts"], "@abrechnung/core": ["libs/core/src/index.ts"], "@abrechnung/redux": ["libs/redux/src/index.ts"], + "@abrechnung/translations": ["libs/translations/src/index.ts"], "@abrechnung/types": ["libs/types/src/index.ts"], "@abrechnung/utils": ["libs/utils/src/index.ts"] },