From fcfdc086bf56f6ba367a04cf11558b896dec84ef Mon Sep 17 00:00:00 2001 From: Amine Ben Yedder <61787798+amine-by@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:56:13 +0100 Subject: [PATCH] [Feature] Implemented i18n translations (#108) * Implemented i18n english/french translations and language detection --------- Authored-by: Amine Ben Yedder --- .gitignore | 6 +- .vscode/extensions.json | 5 + app/(auth)/_layout.tsx | 9 +- app/(auth)/login.tsx | 26 +++-- app/(auth)/onboarding.tsx | 12 ++- app/(auth)/register.tsx | 30 +++--- app/(tabs)/_layout.tsx | 9 +- app/(tabs)/account/_layout.tsx | 7 +- app/(tabs)/account/index.tsx | 98 ++++++++++++------- app/(tabs)/home/_layout.tsx | 7 +- app/(tabs)/home/index.tsx | 14 +-- app/(tabs)/repositories/_layout.tsx | 9 +- app/(tabs)/repositories/index.tsx | 6 +- app/_layout.tsx | 6 +- package.json | 3 + .../ConfirmationCodeModal/index.tsx | 17 ++-- src/components/ConfirmationModal/index.tsx | 4 +- src/lib/i18n.ts | 13 +++ src/locales/en/account.json | 71 ++++++++++++++ src/locales/en/commons.json | 3 + src/locales/en/components.json | 16 +++ src/locales/en/home.json | 12 +++ src/locales/en/index.ts | 21 ++++ src/locales/en/layouts.json | 12 +++ src/locales/en/login.json | 21 ++++ src/locales/en/onboarding.json | 9 ++ src/locales/en/register.json | 28 ++++++ src/locales/en/repositories.json | 4 + src/locales/fr/account.json | 71 ++++++++++++++ src/locales/fr/commons.json | 3 + src/locales/fr/components.json | 16 +++ src/locales/fr/home.json | 12 +++ src/locales/fr/index.ts | 21 ++++ src/locales/fr/layouts.json | 12 +++ src/locales/fr/login.json | 21 ++++ src/locales/fr/onboarding.json | 9 ++ src/locales/fr/register.json | 28 ++++++ src/locales/fr/repositories.json | 4 + src/locales/index.ts | 4 + src/theme/ThemeSwitcher/index.tsx | 6 +- yarn.lock | 39 ++++++++ 41 files changed, 631 insertions(+), 93 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 src/lib/i18n.ts create mode 100644 src/locales/en/account.json create mode 100644 src/locales/en/commons.json create mode 100644 src/locales/en/components.json create mode 100644 src/locales/en/home.json create mode 100644 src/locales/en/index.ts create mode 100644 src/locales/en/layouts.json create mode 100644 src/locales/en/login.json create mode 100644 src/locales/en/onboarding.json create mode 100644 src/locales/en/register.json create mode 100644 src/locales/en/repositories.json create mode 100644 src/locales/fr/account.json create mode 100644 src/locales/fr/commons.json create mode 100644 src/locales/fr/components.json create mode 100644 src/locales/fr/home.json create mode 100644 src/locales/fr/index.ts create mode 100644 src/locales/fr/layouts.json create mode 100644 src/locales/fr/login.json create mode 100644 src/locales/fr/onboarding.json create mode 100644 src/locales/fr/register.json create mode 100644 src/locales/fr/repositories.json create mode 100644 src/locales/index.ts diff --git a/.gitignore b/.gitignore index 00c1480..13a36bc 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,8 @@ src/api/generated-api.* .env .env.production -devices.json \ No newline at end of file +devices.json + +# VSCode +/.vscode/* +!/.vscode/extensions.json \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..e01bf45 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "lokalise.i18n-ally" + ] + } \ No newline at end of file diff --git a/app/(auth)/_layout.tsx b/app/(auth)/_layout.tsx index 91f9230..96668c4 100644 --- a/app/(auth)/_layout.tsx +++ b/app/(auth)/_layout.tsx @@ -1,16 +1,19 @@ +import { useTranslation } from 'react-i18next'; + import { Stack } from '@/layout/Stack'; const AuthStack = () => { + const { t } = useTranslation(); return ( ); diff --git a/app/(auth)/login.tsx b/app/(auth)/login.tsx index e439698..f09572e 100644 --- a/app/(auth)/login.tsx +++ b/app/(auth)/login.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { Formiz, useForm, useFormContext, useFormFields } from '@formiz/core'; import { isEmail } from '@formiz/validations'; +import { useTranslation } from 'react-i18next'; import { Box, Button, @@ -24,17 +25,18 @@ import { useToast } from '@/modules/toast/useToast'; import { useDarkMode } from '@/theme/useDarkMode'; const CardInfoAuthStep = () => { + const { t } = useTranslation(); const loginForm = useFormContext(); const { colorModeValue } = useDarkMode(); return ( - + - Enjoy the features! You can sign in with{' '} + {t('login:card.description')}{' '} loginForm.setValues({ email: 'admin@admin.com' })} @@ -54,6 +56,7 @@ const CardInfoAuthStep = () => { }; const Login = () => { + const { t } = useTranslation(); const loginForm = useForm<{ email: string; code: string; @@ -87,7 +90,7 @@ const Login = () => { validateEmailCodeModal.onOpen(); }, onError: (err) => { - showError('Failed to log in. Please try again'); + showError(t('login:feedbacks.error')); console.error('Authentication error:', err); }, }); @@ -96,14 +99,14 @@ const Login = () => { useAuthLoginValidate(emailToken as string, { onSuccess: () => { validateEmailCodeModal.onClose(); - showSuccess('Successfully logged in'); + showSuccess(t('login:validation.success')); }, onError: () => { emailValidationCodeForm.setValues({ code: null, }); emailValidationCodeForm.setErrors({ - code: 'Code is incorrect, please try again', + code: t('login:validation.error'), }); }, }); @@ -114,9 +117,14 @@ const Login = () => { { colorScheme="brand" full > - Sign in + {t('login:actions.login')} diff --git a/app/(auth)/onboarding.tsx b/app/(auth)/onboarding.tsx index 1f9f372..349fccf 100644 --- a/app/(auth)/onboarding.tsx +++ b/app/(auth)/onboarding.tsx @@ -1,4 +1,5 @@ import { useRouter } from 'expo-router'; +import { useTranslation } from 'react-i18next'; import { Image } from 'react-native'; import { Button, Icon, Stack, Text } from 'react-native-ficus-ui'; @@ -6,6 +7,7 @@ import { useDarkMode } from '@/theme/useDarkMode'; const Onboarding = () => { const router = useRouter(); + const { t } = useTranslation(); const { colorModeValue, toggleColorMode, colorMode } = useDarkMode(); // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -27,14 +29,14 @@ const Onboarding = () => { width: '100%', height: 80, }} - accessibilityLabel="Start UI Native Logo" + accessibilityLabel={t('onboarding:logo')} /> - An opinionated UI starter with Expo, Ficus UI, React Query & Formiz + {t('onboarding:description')} @@ -44,14 +46,14 @@ const Onboarding = () => { onPress={() => router.push('/register')} colorScheme="brand" > - Sign up with mail + {t('onboarding:actions.register')} - Already an account? + {t('onboarding:actions.alreadyHaveAnAccount')} diff --git a/app/(auth)/register.tsx b/app/(auth)/register.tsx index 18d5cd9..edc71b1 100644 --- a/app/(auth)/register.tsx +++ b/app/(auth)/register.tsx @@ -3,6 +3,7 @@ import { useRef, useState } from 'react'; import { Formiz, useForm, useFormFields } from '@formiz/core'; import { isEmail } from '@formiz/validations'; import { useRouter } from 'expo-router'; +import { useTranslation } from 'react-i18next'; import { TextInput } from 'react-native'; import { Box, Button, Stack, Text, useDisclosure } from 'react-native-ficus-ui'; @@ -22,17 +23,17 @@ import { focus } from '@/utils/formUtils'; const CardWarningRegister = () => { const router = useRouter(); + const { t } = useTranslation(); const { colorModeValue } = useDarkMode(); return ( - + - This is a read-only demo, but you can Sign in to test some of the - features. Just remember, no changes can be made. Enjoy the features! + {t('register:card.description')} @@ -50,6 +51,7 @@ const CardWarningRegister = () => { const Register = () => { const { showError, showSuccess } = useToast(); + const { t } = useTranslation(); const nameRef = useRef(null); const validateEmailCodeModal = useDisclosure(); const [emailToken, setEmailToken] = useState(null); @@ -84,8 +86,8 @@ const Register = () => { onError: (err) => { showError( err.response?.data?.message?.startsWith('[DEMO]') - ? 'This is a read-only demo, this action is disabled.' - : 'An error occured during your registration, please try again' + ? t('register:feedbacks.createAccount.error.demo') + : t('register:feedbacks.createAccount.error.default') ); }, }); @@ -94,14 +96,14 @@ const Register = () => { useAuthRegisterValidate(emailToken as string, { onSuccess: () => { validateEmailCodeModal.onClose(); - showSuccess('Successfully logged in'); + showSuccess(t('register:feedbacks.accountValidate.success')); }, onError: () => { emailValidationCodeForm.setValues({ code: null, }); emailValidationCodeForm.setErrors({ - code: 'Code is incorrect, please try again', + code: t('register:feedbacks.accountValidate.error'), }); }, }); @@ -113,12 +115,12 @@ const Register = () => { { { colorScheme="brand" full > - Sign up + {t('register:actions.register')} diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 87cf327..091379c 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -1,24 +1,27 @@ +import { useTranslation } from 'react-i18next'; + import { Tabs } from '@/layout/Tabs'; const HomeTabs = () => { + const { t } = useTranslation(); return ( { - return ; + const { t } = useTranslation(); + return ( + + ); }; export default AccountStack; diff --git a/app/(tabs)/account/index.tsx b/app/(tabs)/account/index.tsx index 9e56e56..9348a1a 100644 --- a/app/(tabs)/account/index.tsx +++ b/app/(tabs)/account/index.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { Formiz, useForm, useFormFields } from '@formiz/core'; import { isEmail } from '@formiz/validations'; +import { useTranslation } from 'react-i18next'; import { Box, Button, @@ -34,6 +35,7 @@ import { useDarkMode } from '@/theme/useDarkMode'; const Account = () => { const logout = useAuthStore((state) => state.logout); + const { t } = useTranslation(); const { account, isLoading, isError, refetch: refetchAccount } = useAccount(); const { showError, showSuccess, showInfo } = useToast(); @@ -55,14 +57,14 @@ const Account = () => { const { updateAccount, isLoading: isUpdatingAccount } = useAccountUpdate({ onSuccess: () => { - showSuccess('Account updated'); + showSuccess(t('account:feedbacks.updateAccount.success')); refetchAccount(); }, onError: (err) => { showError( err.response?.data?.message?.startsWith('[DEMO]') - ? 'This is a read-only demo, this action is disabled.' - : 'An error occured during account update, please try again' + ? t('account:feedbacks.updateAccount.error.demo') + : t('account:feedbacks.updateAccount.error.default') ); }, }); @@ -76,8 +78,8 @@ const Account = () => { onError: (err) => { showError( err.response?.data?.message?.startsWith('[DEMO]') - ? 'This is a read-only demo, this action is disabled.' - : 'An error occured during account email update, please try again' + ? t('account:feedbacks.updateAccountEmail.error.demo') + : t('account:feedbacks.updateAccountEmail.error.default') ); }, }); @@ -87,14 +89,14 @@ const Account = () => { onSuccess: () => { updateEmailCodeModal.onClose(); refetchAccount(); - showSuccess('Account email updated'); + t('account:feedbacks.updateAccountEmailValidate.success'); }, onError: () => { emailValidationCodeForm.setValues({ code: null, }); emailValidationCodeForm.setErrors({ - code: 'Code is incorrect, please try again', + code: t('account:feedbacks.updateAccountEmailValidate.error'), }); }, }); @@ -112,7 +114,7 @@ const Account = () => { const deleteAccountForm = useForm({ onValidSubmit: () => { deleteAccountModal.onClose(); - showInfo('No delete account api yet on Start UI V2'); + showInfo(t('account:confirmationModals.deleteAccount.submit')); }, }); @@ -128,6 +130,10 @@ const Account = () => { fields: ['confirmation'] as const, }); + const handlerValue = t( + 'account:confirmationModals.deleteAccount.input.validations.isValid.handlerValue' + ); + if (isLoading) { return ; } @@ -135,7 +141,9 @@ const Account = () => { if (isError || !account) { return ( - + ); } @@ -146,14 +154,14 @@ const Account = () => { - Profile informations + {t('account:sections.profile.title')} { isLoading={isUpdatingAccount} full > - Update + {t('commons:actions.update')} @@ -177,18 +185,20 @@ const Account = () => { /> - Update your email + {t('account:sections.email.title')} { isDisabled={email === account.email} full > - Update + {t('commons:actions.update')} {email === account.email ? ( - This is your current email + {t('account:sections.email.feedbacks.isEmail')} ) : ( )} @@ -241,7 +251,9 @@ const Account = () => { /> - Preferences + + {t('account:sections.preferences.title')} + { borderWidth={1} borderColor={colorModeValue('gray.200', 'gray.600')} > - Logout + {t('account:actions.logout')} { colorScheme="error" full > - Delete account + {t('account:actions.deleteAccount')} @@ -290,10 +302,10 @@ const Account = () => { /> { /> deleteAccountForm.submit()} onCancel={deleteAccountModal.onClose} @@ -314,26 +328,36 @@ const Account = () => { > - + - This action is irreversible and immediate. All your data will be - will be deleted immediately. You will have to recreate an - account. + {t('account:confirmationModals.deleteAccount.card.description')} value === 'DELETION', - message: 'Please enter "DELETION" to validate', + handler: (value) => value === handlerValue, + message: t( + 'account:confirmationModals.deleteAccount.input.validations.isValid.message', + { + handlerValue, + } + ), }, ]} /> diff --git a/app/(tabs)/home/_layout.tsx b/app/(tabs)/home/_layout.tsx index 37ed017..866c49c 100644 --- a/app/(tabs)/home/_layout.tsx +++ b/app/(tabs)/home/_layout.tsx @@ -1,7 +1,12 @@ +import { useTranslation } from 'react-i18next'; + import { Stack } from '@/layout/Stack'; const HomeStack = () => { - return ; + const { t } = useTranslation(); + return ( + + ); }; export default HomeStack; diff --git a/app/(tabs)/home/index.tsx b/app/(tabs)/home/index.tsx index 4f303b0..b3d72e8 100644 --- a/app/(tabs)/home/index.tsx +++ b/app/(tabs)/home/index.tsx @@ -1,4 +1,5 @@ import { useRouter } from 'expo-router'; +import { useTranslation } from 'react-i18next'; import { Box, Stack, Text, VStack } from 'react-native-ficus-ui'; import { ButtonIcon } from '@/components/ButtonIcon'; @@ -6,6 +7,7 @@ import { useDarkMode } from '@/theme/useDarkMode'; const Home = () => { const router = useRouter(); + const { t } = useTranslation(); const { colorModeValue, getThemeColor } = useDarkMode(); return ( @@ -14,16 +16,16 @@ const Home = () => { fontWeight="bold" color={colorModeValue('black', 'gray.50')} > - Welcome to 🚀 Start UI [native] + {t('home:welcome.title')} - An opinionated UI starter with Expo, Ficus UI, Zodios & Formiz + {t('home:welcome.description')} - - From the{' '} + {t('home:welcome.from')}{' '} - BearStudio Team + {t('home:welcome.author')} @@ -45,7 +47,7 @@ const Home = () => { borderColor={colorModeValue('gray.200', 'gray.600')} full > - Github Repository + {t('home:links.github')} { borderColor={colorModeValue('gray.200', 'gray.600')} full > - Open issue + {t('home:links.openIssue')} diff --git a/app/(tabs)/repositories/_layout.tsx b/app/(tabs)/repositories/_layout.tsx index ddcbb6d..592aac0 100644 --- a/app/(tabs)/repositories/_layout.tsx +++ b/app/(tabs)/repositories/_layout.tsx @@ -1,7 +1,14 @@ +import { useTranslation } from 'react-i18next'; + import { Stack } from '@/layout/Stack'; const RepositoriesStack = () => { - return ; + const { t } = useTranslation(); + return ( + + ); }; export default RepositoriesStack; diff --git a/app/(tabs)/repositories/index.tsx b/app/(tabs)/repositories/index.tsx index bf4b930..4e2713e 100644 --- a/app/(tabs)/repositories/index.tsx +++ b/app/(tabs)/repositories/index.tsx @@ -1,4 +1,5 @@ import { Link, useRouter } from 'expo-router'; +import { useTranslation } from 'react-i18next'; import { Box, Button, @@ -14,6 +15,7 @@ import { useDarkMode } from '@/theme/useDarkMode'; const Repositories = () => { const router = useRouter(); + const { t } = useTranslation(); const { colorModeValue } = useDarkMode(); const repositories = useRepositories(); @@ -24,7 +26,7 @@ const Repositories = () => { {repositories.isSuccess && !repositories.data.pages.flatMap((page) => page.items).length && ( - No repositories + {t('repositories:noRepositories')} )} @@ -74,7 +76,7 @@ const Repositories = () => { onPress={() => repositories.fetchNextPage()} isLoading={repositories.isFetchingNextPage} > - Load more + {t('repositories:loadMore')} )} diff --git a/app/_layout.tsx b/app/_layout.tsx index dddef18..b1a612b 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -2,9 +2,11 @@ import React from 'react'; import { QueryClientProvider } from '@tanstack/react-query'; import { Slot } from 'expo-router'; +import { I18nextProvider } from 'react-i18next'; import { ThemeProvider } from 'react-native-ficus-ui'; import { queryClient } from '@/api/query-client'; +import i18n from '@/lib/i18n'; import useProtectedRoute from '@/modules/auth/auth.hook'; import theme from '@/theme'; @@ -14,7 +16,9 @@ export default function RootLayout() { return ( - + + + ); diff --git a/package.json b/package.json index 93c7ea8..4fae9b2 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@formiz/core": "2.0.0-beta.11", "@formiz/validations": "2.0.0-beta.2", "@lukemorales/query-key-factory": "1.3.1", + "@os-team/i18next-react-native-language-detector": "1.0.34", "@react-native-async-storage/async-storage": "1.23.1", "@tanstack/react-query": "4.29.14", "@zodios/core": "10.9.6", @@ -47,9 +48,11 @@ "expo-splash-screen": "0.27.4", "expo-status-bar": "1.12.1", "expo-updates": "0.25.14", + "i18next": "23.12.2", "os": "0.1.2", "prettier": "3.1.1", "react": "18.2.0", + "react-i18next": "15.0.1", "react-native": "0.74.1", "react-native-animatable": "1.3.3", "react-native-ble-plx": "2.0.3", diff --git a/src/components/ConfirmationCodeModal/index.tsx b/src/components/ConfirmationCodeModal/index.tsx index 717feb0..e3c5eaf 100644 --- a/src/components/ConfirmationCodeModal/index.tsx +++ b/src/components/ConfirmationCodeModal/index.tsx @@ -1,6 +1,7 @@ import { FC, PropsWithChildren, useEffect, useRef } from 'react'; import { Form, Formiz } from '@formiz/core'; +import { useTranslation } from 'react-i18next'; import { BottomSheetTextInput } from '@gorhom/bottom-sheet'; import { TextInput } from 'react-native'; import { Box, Text, TouchableOpacity } from 'react-native-ficus-ui'; @@ -23,6 +24,7 @@ export const ConfirmationCodeModal: FC< PropsWithChildren > = ({ isOpen, onClose, form, email, isLoadingConfirm = false }) => { const { colorModeValue } = useDarkMode(); + const { t } = useTranslation(); const codeInputRef = useRef(null); useEffect(() => { @@ -33,9 +35,9 @@ export const ConfirmationCodeModal: FC< return ( form.submit()} onCancel={onClose} @@ -48,7 +50,7 @@ export const ConfirmationCodeModal: FC< { if (code?.length === 6) { @@ -63,10 +65,13 @@ export const ConfirmationCodeModal: FC< /> - + - To quickly validate, use the code{' '} + {t('components:ConfirmationCodeModal.card.description')}{' '} { diff --git a/src/components/ConfirmationModal/index.tsx b/src/components/ConfirmationModal/index.tsx index 56dbf3d..c515a70 100644 --- a/src/components/ConfirmationModal/index.tsx +++ b/src/components/ConfirmationModal/index.tsx @@ -1,5 +1,6 @@ import { FC, PropsWithChildren } from 'react'; +import { useTranslation } from 'react-i18next'; import { GestureResponderEvent } from 'react-native'; import { Box, @@ -62,6 +63,7 @@ export const ConfirmationModal: FC< ...rest }) => { const { colorModeValue, getThemeColor } = useDarkMode(); + const { t } = useTranslation(); return ( - Cancel + {t('commons:actions.cancel')} diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts new file mode 100644 index 0000000..da21413 --- /dev/null +++ b/src/lib/i18n.ts @@ -0,0 +1,13 @@ +import languageDetector from '@os-team/i18next-react-native-language-detector'; +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; + +import resources from '@/locales'; + +i18n.use(initReactI18next).use(languageDetector).init({ + resources, + compatibilityJSON: 'v3', + fallbackLng: 'en', +}); + +export default i18n; diff --git a/src/locales/en/account.json b/src/locales/en/account.json new file mode 100644 index 0000000..85f4385 --- /dev/null +++ b/src/locales/en/account.json @@ -0,0 +1,71 @@ +{ + "feedbacks": { + "updateAccount": { + "success": "Account updated", + "error": { + "demo": "This is a read-only demo, this action is disabled.", + "default": "An error occured during account update, please try again" + } + }, + "updateAccountEmail": { + "error": { + "demo": "This is a read-only demo, this action is disabled.", + "default": "An error occured during account email update, please try again" + } + }, + "updateAccountEmailValidate": { + "success": "Account email updated", + "error": "Code is incorrect, please try again" + } + }, + "sections": { + "profile": { + "title": "Profile informations", + "input": { "label": "Name", "required": "Name is required" } + }, + "email": { + "title": "Update your email", + "input": { + "label": "Mail address", + "required": "Mail is required", + "validations": { + "email": "Mail is invalid" + } + }, + "feedbacks": { "isEmail": "This is your current email" } + }, + "preferences": { "title": "Preferences" } + }, + "confirmationModals": { + "logout": { + "title": "Logout", + "description": "Do you really want to logout from the application?", + "confirmLabel": "Logout" + }, + "deleteAccount": { + "title": "Delete account", + "description": "Do you really want to delete your account?", + "confirmLabel": "Confirm the deletion of account", + "submit": "No delete account api yet on Start UI V2", + "card": { + "title": "Warning", + "description": "This action is irreversible and immediate. All your data will be will be deleted immediately. You will have to recreate an account." + }, + "input": { + "label": "Enter \"{{handlerValue}}\"", + "required": "Confirmation required", + "validations": { + "isValid": { + "message": "Please enter \"{{handlerValue}}\" to validate", + "handlerValue": "DELETION" + } + } + } + } + }, + "actions": { + "retry": "Retry", + "logout": "Logout", + "deleteAccount": "Delete account" + } +} diff --git a/src/locales/en/commons.json b/src/locales/en/commons.json new file mode 100644 index 0000000..19d956a --- /dev/null +++ b/src/locales/en/commons.json @@ -0,0 +1,3 @@ +{ + "actions": { "cancel": "Cancel", "update": "Update" } +} diff --git a/src/locales/en/components.json b/src/locales/en/components.json new file mode 100644 index 0000000..f294ff4 --- /dev/null +++ b/src/locales/en/components.json @@ -0,0 +1,16 @@ +{ + "ConfirmationCodeModal": { + "title": "Check your inbox for the code", + "description": "We've sent a 6-character code to {{email}}. The code expires shortly (5 minutes).", + "confirmLabel": "Confirm", + "required": "Validation code is required", + "card": { + "title": "Demo mode", + "description": "To quickly validate, use the code" + } + }, + "ThemeSwitcher": { + "light": "Light mode", + "dark": "Dark mode" + } +} diff --git a/src/locales/en/home.json b/src/locales/en/home.json new file mode 100644 index 0000000..8c1ab85 --- /dev/null +++ b/src/locales/en/home.json @@ -0,0 +1,12 @@ +{ + "welcome": { + "title": "Welcome to 🚀 Start UI [native]", + "description": "An opinionated UI starter with Expo, Ficus UI, Zodios & Formiz", + "from": "- From the", + "author": "BearStudio Team" + }, + "links": { + "github": "GitHub Repository", + "openIssue": "Open Issue" + } +} diff --git a/src/locales/en/index.ts b/src/locales/en/index.ts new file mode 100644 index 0000000..34cef6c --- /dev/null +++ b/src/locales/en/index.ts @@ -0,0 +1,21 @@ +import account from './account.json'; +import commons from './commons.json'; +import components from './components.json'; +import home from './home.json'; +import layouts from './layouts.json'; +import login from './login.json'; +import onboarding from './onboarding.json'; +import register from './register.json'; +import repositories from './repositories.json'; + +export default { + login, + onboarding, + register, + layouts, + repositories, + home, + account, + components, + commons, +}; diff --git a/src/locales/en/layouts.json b/src/locales/en/layouts.json new file mode 100644 index 0000000..2e3968c --- /dev/null +++ b/src/locales/en/layouts.json @@ -0,0 +1,12 @@ +{ + "auth": { + "onboarding": "Onboarding", + "login": "Login", + "register": "Create Account" + }, + "tabs": { + "account": "Account", + "home": "Home", + "repositories": "Repositories" + } +} diff --git a/src/locales/en/login.json b/src/locales/en/login.json new file mode 100644 index 0000000..d8f5102 --- /dev/null +++ b/src/locales/en/login.json @@ -0,0 +1,21 @@ +{ + "card": { + "title": "Demo mode", + "description": "Enjoy the features! You can sign in with" + }, + "feedbacks": { + "error": "Failed to log in. Please try again" + }, + "validation": { + "success": "Successfully logged in", + "error": "Code is incorrect, please try again" + }, + "input": { + "label": "Mail address", + "required": "Mail is required", + "validations": { + "email": "Mail is invalid" + } + }, + "actions": { "login": "Sign in" } +} diff --git a/src/locales/en/onboarding.json b/src/locales/en/onboarding.json new file mode 100644 index 0000000..501492d --- /dev/null +++ b/src/locales/en/onboarding.json @@ -0,0 +1,9 @@ +{ + "logo": "Start UI Native Logo", + "description": "An opinionated UI starter with Expo, Ficus UI, Zodios & Formiz", + "actions": { + "login": "Sign in", + "alreadyHaveAnAccount": "Already have an account?", + "register": "Sign up with mail" + } +} diff --git a/src/locales/en/register.json b/src/locales/en/register.json new file mode 100644 index 0000000..98421d3 --- /dev/null +++ b/src/locales/en/register.json @@ -0,0 +1,28 @@ +{ + "card": { + "title": "Demo mode", + "description": "This is a read-only demo, but you can Sign in to test some of the features. Just remember, no changes can be made. Enjoy the features!", + "actions": { "login": "Sign in" } + }, + "feedbacks": { + "createAccount": { + "error": { + "demo": "This is a read-only demo, this action is disabled.", + "default": "An error occured during your registration, please try again" + } + }, + "accountValidate": { + "success": "Successfully logged in", + "error": "Code is incorrect, please try again" + } + }, + "inputs": { + "email": { + "label": "Mail address", + "required": "Mail is required", + "validations": { "email": "Mail is invalid" } + }, + "name": { "label": "Name", "required": "Name is required" } + }, + "actions": { "register": "Sign up" } +} diff --git a/src/locales/en/repositories.json b/src/locales/en/repositories.json new file mode 100644 index 0000000..826526c --- /dev/null +++ b/src/locales/en/repositories.json @@ -0,0 +1,4 @@ +{ + "noRepositories": "No repositories", + "loadMore": "Load more" +} diff --git a/src/locales/fr/account.json b/src/locales/fr/account.json new file mode 100644 index 0000000..80c9a33 --- /dev/null +++ b/src/locales/fr/account.json @@ -0,0 +1,71 @@ +{ + "feedbacks": { + "updateAccount": { + "success": "Compte mis à jour", + "error": { + "demo": "Ceci est une démo en lecture seule, cette action est désactivée.", + "default": "Une erreur est survenue lors de la mise à jour du compte, veuillez réessayer" + } + }, + "updateAccountEmail": { + "error": { + "demo": "Ceci est une démo en lecture seule, cette action est désactivée.", + "default": "Une erreur est survenue lors de la mise à jour de l'email du compte, veuillez réessayer" + } + }, + "updateAccountEmailValidate": { + "success": "Email du compte mis à jour", + "error": "Le code est incorrect, veuillez réessayer" + } + }, + "sections": { + "profile": { + "title": "Informations du profil", + "input": { "label": "Nom", "required": "Le nom est requis" } + }, + "email": { + "title": "Mettre à jour votre email", + "input": { + "label": "Adresse email", + "required": "L'email est requis", + "validations": { + "email": "L'email est invalide" + } + }, + "feedbacks": { "isEmail": "Ceci est votre email actuel" } + }, + "preferences": { "title": "Préférences" } + }, + "confirmationModals": { + "logout": { + "title": "Déconnexion", + "description": "Voulez-vous vraiment vous déconnecter de l'application?", + "confirmLabel": "Déconnexion" + }, + "deleteAccount": { + "title": "Supprimer le compte", + "description": "Voulez-vous vraiment supprimer votre compte?", + "confirmLabel": "Confirmer la suppression du compte", + "submit": "Pas encore d'API de suppression de compte sur Start UI V2", + "card": { + "title": "Avertissement", + "description": "Cette action est irréversible et immédiate. Toutes vos données seront supprimées immédiatement. Vous devrez recréer un compte." + }, + "input": { + "label": "Entrez \"{{handlerValue}}\"", + "required": "Confirmation requise", + "validations": { + "isValid": { + "message": "Veuillez entrer \"{{handlerValue}}\" pour valider", + "handlerValue": "SUPPRESSION" + } + } + } + } + }, + "actions": { + "retry": "Réessayer", + "logout": "Déconnexion", + "deleteAccount": "Supprimer le compte" + } +} diff --git a/src/locales/fr/commons.json b/src/locales/fr/commons.json new file mode 100644 index 0000000..85ebb50 --- /dev/null +++ b/src/locales/fr/commons.json @@ -0,0 +1,3 @@ +{ + "actions": { "cancel": "Annuler", "update": "Mettre à jour" } +} diff --git a/src/locales/fr/components.json b/src/locales/fr/components.json new file mode 100644 index 0000000..37778be --- /dev/null +++ b/src/locales/fr/components.json @@ -0,0 +1,16 @@ +{ + "ConfirmationCodeModal": { + "title": "Vérifiez votre boîte de réception pour le code", + "description": "Nous avons envoyé un code à 6 caractères à {{email}}. Le code expire bientôt (5 minutes).", + "confirmLabel": "Confirmer", + "required": "Le code de validation est requis", + "card": { + "title": "Mode démo", + "description": "Pour valider rapidement, utilisez le code" + } + }, + "ThemeSwitcher": { + "light": "Mode clair", + "dark": "Mode sombre" + } +} diff --git a/src/locales/fr/home.json b/src/locales/fr/home.json new file mode 100644 index 0000000..1b18fba --- /dev/null +++ b/src/locales/fr/home.json @@ -0,0 +1,12 @@ +{ + "welcome": { + "title": "Bienvenue sur 🚀 Start UI [native]", + "description": "Un starter UI dogmatique avec Expo, Ficus UI, Zodios & Formiz", + "from": "- De la part de", + "author": "L'équipe BearStudio" + }, + "links": { + "github": "Dépôt GitHub", + "openIssue": "Ouvrir un problème" + } +} diff --git a/src/locales/fr/index.ts b/src/locales/fr/index.ts new file mode 100644 index 0000000..34cef6c --- /dev/null +++ b/src/locales/fr/index.ts @@ -0,0 +1,21 @@ +import account from './account.json'; +import commons from './commons.json'; +import components from './components.json'; +import home from './home.json'; +import layouts from './layouts.json'; +import login from './login.json'; +import onboarding from './onboarding.json'; +import register from './register.json'; +import repositories from './repositories.json'; + +export default { + login, + onboarding, + register, + layouts, + repositories, + home, + account, + components, + commons, +}; diff --git a/src/locales/fr/layouts.json b/src/locales/fr/layouts.json new file mode 100644 index 0000000..00fcffd --- /dev/null +++ b/src/locales/fr/layouts.json @@ -0,0 +1,12 @@ +{ + "auth": { + "onboarding": "Introduction", + "login": "Connexion", + "register": "Créer un compte" + }, + "tabs": { + "account": "Compte", + "home": "Accueil", + "repositories": "Dépôts" + } +} diff --git a/src/locales/fr/login.json b/src/locales/fr/login.json new file mode 100644 index 0000000..580a020 --- /dev/null +++ b/src/locales/fr/login.json @@ -0,0 +1,21 @@ +{ + "card": { + "title": "Mode démo", + "description": "Profitez des fonctionnalités ! Vous pouvez vous connecter avec" + }, + "feedbacks": { + "error": "Échec de la connexion. Veuillez réessayer" + }, + "validation": { + "success": "Connexion réussie", + "error": "Le code est incorrect, veuillez réessayer" + }, + "input": { + "label": "Adresse email", + "required": "L'email est requis", + "validations": { + "email": "L'email est invalide" + } + }, + "actions": { "login": "Se connecter" } +} diff --git a/src/locales/fr/onboarding.json b/src/locales/fr/onboarding.json new file mode 100644 index 0000000..5d30a76 --- /dev/null +++ b/src/locales/fr/onboarding.json @@ -0,0 +1,9 @@ +{ + "logo": "Logo Start UI Native", + "description": "Un starter UI dogmatique avec Expo, Ficus UI, Zodios & Formiz", + "actions": { + "login": "Se connecter", + "alreadyHaveAnAccount": "Vous avez déjà un compte ?", + "register": "S'inscrire avec un email" + } +} diff --git a/src/locales/fr/register.json b/src/locales/fr/register.json new file mode 100644 index 0000000..667063d --- /dev/null +++ b/src/locales/fr/register.json @@ -0,0 +1,28 @@ +{ + "card": { + "title": "Mode démo", + "description": "Ceci est une démo en lecture seule, mais vous pouvez vous connecter pour tester certaines fonctionnalités. N'oubliez pas, aucune modification ne peut être apportée. Profitez des fonctionnalités !", + "actions": { "login": "Se connecter" } + }, + "feedbacks": { + "createAccount": { + "error": { + "demo": "Ceci est une démo en lecture seule, cette action est désactivée.", + "default": "Une erreur est survenue lors de votre inscription, veuillez réessayer" + } + }, + "accountValidate": { + "success": "Connexion réussie", + "error": "Le code est incorrect, veuillez réessayer" + } + }, + "inputs": { + "email": { + "label": "Adresse email", + "required": "L'email est requis", + "validations": { "email": "L'email est invalide" } + }, + "name": { "label": "Nom", "required": "Le nom est requis" } + }, + "actions": { "register": "S'inscrire" } +} diff --git a/src/locales/fr/repositories.json b/src/locales/fr/repositories.json new file mode 100644 index 0000000..6cbc3a3 --- /dev/null +++ b/src/locales/fr/repositories.json @@ -0,0 +1,4 @@ +{ + "noRepositories": "Aucun dépôt", + "loadMore": "Charger plus" +} diff --git a/src/locales/index.ts b/src/locales/index.ts new file mode 100644 index 0000000..05ea7c7 --- /dev/null +++ b/src/locales/index.ts @@ -0,0 +1,4 @@ +import en from './en'; +import fr from './fr'; + +export default { en, fr }; diff --git a/src/theme/ThemeSwitcher/index.tsx b/src/theme/ThemeSwitcher/index.tsx index 7dd42ce..b38a714 100644 --- a/src/theme/ThemeSwitcher/index.tsx +++ b/src/theme/ThemeSwitcher/index.tsx @@ -1,10 +1,12 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Stack, Switch, Text } from 'react-native-ficus-ui'; import { useDarkMode } from '@/theme/useDarkMode'; export default function ThemeSwitcher() { + const { t } = useTranslation(); const { colorMode, toggleColorMode, colorModeValue } = useDarkMode(); return ( @@ -14,7 +16,7 @@ export default function ThemeSwitcher() { fontWeight="500" color={colorModeValue('gray.900', 'gray.400')} > - Light mode + {t('components:ThemeSwitcher.light')} - Dark mode + {t('components:ThemeSwitcher.dark')} ); diff --git a/yarn.lock b/yarn.lock index 5ace6a7..eed0527 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1332,6 +1332,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.24.8": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" + integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.0.0", "@babel/template@^7.20.7", "@babel/template@^7.22.15", "@babel/template@^7.23.9", "@babel/template@^7.24.0", "@babel/template@^7.24.6": version "7.24.6" resolved "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz" @@ -2223,6 +2230,11 @@ dependencies: semver "^7.3.5" +"@os-team/i18next-react-native-language-detector@^1.0.34": + version "1.0.34" + resolved "https://registry.yarnpkg.com/@os-team/i18next-react-native-language-detector/-/i18next-react-native-language-detector-1.0.34.tgz#fd7a44eebccf8ca621cbe6535066a51366778cad" + integrity sha512-3i4KhddewzlLnCoYxNnovfk1CyagFOOy5gQ7ML5RruqfSFovCBl94GUVW1W6Bku9nZqK4vVKOy76VHDik4wrLQ== + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" @@ -6606,6 +6618,13 @@ hosted-git-info@^3.0.2: dependencies: lru-cache "^6.0.0" +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + html-tags@^3.1.0: version "3.3.1" resolved "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz" @@ -6635,6 +6654,13 @@ human-signals@^2.1.0: resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +i18next@^23.12.2: + version "23.12.2" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.12.2.tgz#c5b44bb95e4d4a5908a51577fa06c63dc2f650a4" + integrity sha512-XIeh5V+bi8SJSWGL3jqbTEBW5oD6rbP5L+E7dVQh1MNTxxYef0x15rhJVcRb7oiuq4jLtgy2SD8eFlf6P2cmqg== + dependencies: + "@babel/runtime" "^7.23.2" + ieee754@^1.1.13: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" @@ -8789,6 +8815,14 @@ react-freeze@^1.0.0: resolved "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.3.tgz" integrity sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g== +react-i18next@^15.0.1: + version "15.0.1" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.0.1.tgz#fc662d93829ecb39683fe2757a47ebfbc5c912a0" + integrity sha512-NwxLqNM6CLbeGA9xPsjits0EnXdKgCRSS6cgkgOdNcPXqL+1fYNl8fBg1wmnnHvFy812Bt4IWTPE9zjoPmFj3w== + dependencies: + "@babel/runtime" "^7.24.8" + html-parse-stringify "^3.0.1" + react-is@18.1.0: version "18.1.0" resolved "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz" @@ -10470,6 +10504,11 @@ vlq@^1.0.0: resolved "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz" integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + walker@^1.0.7: version "1.0.8" resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz"