Skip to content

Commit

Permalink
[Feature] Implemented i18n translations (#108)
Browse files Browse the repository at this point in the history
* Implemented i18n english/french translations and language detection

---------

Authored-by: Amine Ben Yedder <[email protected]>
  • Loading branch information
amine-by authored Nov 7, 2024
1 parent adc7880 commit fcfdc08
Show file tree
Hide file tree
Showing 41 changed files with 631 additions and 93 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ src/api/generated-api.*
.env
.env.production

devices.json
devices.json

# VSCode
/.vscode/*
!/.vscode/extensions.json
5 changes: 5 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"lokalise.i18n-ally"
]
}
9 changes: 6 additions & 3 deletions app/(auth)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { useTranslation } from 'react-i18next';

import { Stack } from '@/layout/Stack';

const AuthStack = () => {
const { t } = useTranslation();
return (
<Stack
screens={[
{
route: 'onboarding',
title: 'Onboarding',
title: t('layouts:auth.onboarding'),
options: { headerShown: false },
},
{ route: 'login', title: 'Login' },
{ route: 'register', title: 'Create Account' },
{ route: 'login', title: t('layouts:auth.login') },
{ route: 'register', title: t('layouts:auth.register') },
]}
/>
);
Expand Down
26 changes: 17 additions & 9 deletions app/(auth)/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 (
<CardStatus type="info" title="Demo mode" mt="md">
<CardStatus type="info" title={t('login:card.title')} mt="md">
<Box flexDirection="row" alignItems="center" flexWrap="wrap">
<Text
fontSize="lg"
color={colorModeValue('gray.800', 'gray.50')}
my="sm"
>
Enjoy the features! You can sign in with{' '}
{t('login:card.description')}{' '}
</Text>
<TouchableOpacity
onPress={() => loginForm.setValues({ email: '[email protected]' })}
Expand All @@ -54,6 +56,7 @@ const CardInfoAuthStep = () => {
};

const Login = () => {
const { t } = useTranslation();
const loginForm = useForm<{
email: string;
code: string;
Expand Down Expand Up @@ -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);
},
});
Expand All @@ -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'),
});
},
});
Expand All @@ -114,9 +117,14 @@ const Login = () => {
<Content>
<FieldInput
name="email"
label="Mail address"
required="Mail is required"
validations={[{ handler: isEmail(), message: 'Mail is invalid' }]}
label={t('login:input.label')}
required={t('login:input.required')}
validations={[
{
handler: isEmail(),
message: t('login:input.validations.email'),
},
]}
componentProps={{
autoCapitalize: 'none',
keyboardType: 'email-address',
Expand All @@ -133,7 +141,7 @@ const Login = () => {
colorScheme="brand"
full
>
Sign in
{t('login:actions.login')}
</Button>
</Footer>
</Formiz>
Expand Down
12 changes: 7 additions & 5 deletions app/(auth)/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
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';

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
Expand All @@ -27,14 +29,14 @@ const Onboarding = () => {
width: '100%',
height: 80,
}}
accessibilityLabel="Start UI Native Logo"
accessibilityLabel={t('onboarding:logo')}
/>
<Text
fontSize="lg"
textAlign="center"
color={colorModeValue('gray.900', 'gray.50')}
>
An opinionated UI starter with Expo, Ficus UI, React Query & Formiz
{t('onboarding:description')}
</Text>
</Stack>
<Stack spacing="md" alignItems="center">
Expand All @@ -44,22 +46,22 @@ const Onboarding = () => {
onPress={() => router.push('/register')}
colorScheme="brand"
>
Sign up with mail
{t('onboarding:actions.register')}
</Button>
<Stack direction="row" alignItems="center">
<Text
onPress={handleOpenLogin}
color={colorModeValue('gray.900', 'gray.50')}
>
Already an account?
{t('onboarding:actions.alreadyHaveAnAccount')}
</Text>
<Button onPress={handleOpenLogin} colorScheme="transparent">
<Text
fontWeight="500"
textDecorLine="underline"
color={colorModeValue('gray.900', 'gray.50')}
>
Sign in
{t('onboarding:actions.login')}
</Text>
</Button>
</Stack>
Expand Down
30 changes: 16 additions & 14 deletions app/(auth)/register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -22,17 +23,17 @@ import { focus } from '@/utils/formUtils';

const CardWarningRegister = () => {
const router = useRouter();
const { t } = useTranslation();
const { colorModeValue } = useDarkMode();
return (
<CardStatus type="warning" title="Demo mode" mt="md">
<CardStatus type="warning" title={t('register:card.title')} mt="md">
<Box flexDirection="row" flexWrap="wrap">
<Text
fontSize="lg"
color={colorModeValue('gray.800', 'gray.50')}
my="sm"
>
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')}
</Text>
<Button
onPress={() => router.push('/login')}
Expand All @@ -41,7 +42,7 @@ const CardWarningRegister = () => {
bg={colorModeValue(undefined, 'gray.800')}
variant={colorModeValue('outline', 'solid')}
>
Sign in
{t('register:card.actions.login')}
</Button>
</Box>
</CardStatus>
Expand All @@ -50,6 +51,7 @@ const CardWarningRegister = () => {

const Register = () => {
const { showError, showSuccess } = useToast();
const { t } = useTranslation();
const nameRef = useRef<TextInput>(null);
const validateEmailCodeModal = useDisclosure();
const [emailToken, setEmailToken] = useState<string | null>(null);
Expand Down Expand Up @@ -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')
);
},
});
Expand All @@ -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'),
});
},
});
Expand All @@ -113,12 +115,12 @@ const Register = () => {
<Stack spacing="md">
<FieldInput
name="email"
label="Mail address"
required="Mail is required"
label={t('register:inputs.email.label')}
required={t('register:inputs.email.required')}
validations={[
{
handler: isEmail(),
message: 'Mail is invalid',
message: t('register:inputs.email.validations.email'),
},
]}
componentProps={{
Expand All @@ -134,8 +136,8 @@ const Register = () => {
<FieldInput
ref={nameRef}
name="name"
label="Name"
required="Name is required"
label={t('register:inputs.name.label')}
required={t('register:inputs.name.required')}
componentProps={{
autoCapitalize: 'none',
returnKeyType: 'next',
Expand All @@ -154,7 +156,7 @@ const Register = () => {
colorScheme="brand"
full
>
Sign up
{t('register:actions.register')}
</Button>
</Footer>
</Formiz>
Expand Down
9 changes: 6 additions & 3 deletions app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { useTranslation } from 'react-i18next';

import { Tabs } from '@/layout/Tabs';

const HomeTabs = () => {
const { t } = useTranslation();
return (
<Tabs
screens={[
{
route: 'home',
title: 'Home',
title: t('layouts:tabs.home'),
icon: 'home',
options: { headerShown: false },
},
{
route: 'repositories',
title: 'Repositories',
title: t('layouts:tabs.repositories'),
icon: 'folder',
options: { headerShown: false },
},
{
route: 'account',
title: 'Account',
title: t('layouts:tabs.account'),
icon: 'user',
options: { headerShown: false },
},
Expand Down
7 changes: 6 additions & 1 deletion app/(tabs)/account/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { useTranslation } from 'react-i18next';

import { Stack } from '@/layout/Stack';

const AccountStack = () => {
return <Stack screens={[{ route: 'index', title: 'Account' }]} />;
const { t } = useTranslation();
return (
<Stack screens={[{ route: 'index', title: t('layouts:tabs.account') }]} />
);
};

export default AccountStack;
Loading

0 comments on commit fcfdc08

Please sign in to comment.