Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8693cgf0c - tłumaczenia w aplikacji, konfiguracja testów, dodanie kilku kolorów z figmy, poprawa Primary Button #20

Merged
merged 8 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ yarn-error.*

# typescript
*.tsbuildinfo

# react-native
%ProgramData%
20 changes: 20 additions & 0 deletions App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import i18next from 'i18next';
import commonEN from 'public/locales/en/common.json';
import commonPL from 'public/locales/pl/common.json';
import { initReactI18next } from 'react-i18next';

import { ThemeProvider } from '@/hooks';
import { Components, Home } from '@/screens';
Expand All @@ -8,6 +12,22 @@ import { RootStackParamList } from '@/types/param';
const Stack = createNativeStackNavigator<RootStackParamList>();

const App = () => {
i18next.use(initReactI18next).init({
compatibilityJSON: 'v3',
fallbackLng: 'pl',
interpolation: {
escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
},
resources: {
pl: {
common: commonPL,
},
en: {
common: commonEN,
},
},
});

return (
<NavigationContainer>
<ThemeProvider>
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ If you are new to _react-hook-form_ library then we recommend you create your fo

![2 step](./readme/sort_import_2.png)

## Translations

### Structure

![translations structure](./readme/translations_structure.png)

### To add a new translation file

- create file in `/public/locales/pl` and every other language folder
- import it to the typescript declaration file and add it to the resources as shown below - `/src/types/i18next.d.ts`: ![translations declatarion file](./readme/translations_declaration_file.png)

### Recommended way of translationing

- `Import { t } from 'i18-next';`
- `const { t } = useTranslation('common');`, where `common` is the name of the file from which we want to get a translation (namespace)
- use translated text in the app - `t('Hello', {ns: 'common'})` - where `Hello` is the translated text and common is the name of the file (if we have imported only one namepsace then it can be skipped)

## Start development

- Clone repository
Expand Down
13 changes: 13 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Config } from 'jest';

export default async (): Promise<Config> => {
return {
preset: '@testing-library/react-native',
// testEnvironment: 'node',
moduleDirectories: ['node_modules', '<rootDir>/'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
setupFiles: ['./jest.setup.ts'],
};
};
3 changes: 3 additions & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
jest.mock('@react-native-async-storage/async-storage', () =>
require('@react-native-async-storage/async-storage/jest/async-storage-mock'),
);
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
"@react-navigation/native-stack": "^6.9.17",
"expo": "~49.0.15",
"expo-status-bar": "~1.6.0",
"i18next": "^23.10.1",
"i18next-browser-languagedetector": "^7.2.0",
"react": "18.2.0",
"react-hook-form": "^7.48.2",
"react-native": "0.72.6",
"react-i18next": "^14.1.0",
"react-native": "0.72.10",
"react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0",
"yarn": "^1.22.21",
Expand All @@ -43,10 +46,8 @@
"react-dom": "18.2.0",
"react-native-web": "~0.19.6",
"react-test-renderer": "^18.2.0",
"ts-node": "^10.9.2",
"typescript": "^5.1.3"
},
"jest": {
"preset": "@testing-library/react-native"
},
"private": true
}
3 changes: 3 additions & 0 deletions public/locales/en/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"Hello": "Hello"
}
3 changes: 3 additions & 0 deletions public/locales/pl/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"Hello": "Witamy"
}
Binary file added readme/translations_declaration_file.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added readme/translations_structure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 6 additions & 5 deletions src/components/Button/PrimaryButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { fireEvent, render, screen } from '@testing-library/react-native';
import { fireEvent, screen } from '@testing-library/react-native';

import PrimaryButton from './PrimaryButton';
import { PrimaryButton } from '@/components/Button';
import renderWithTheme from '@/tests/renderWithTheme';

const onPress = jest.fn();

describe('PrimaryButton component tests', () => {
it('properly renders button with provided title', () => {
render(<PrimaryButton title="test" onPress={onPress} />);
it('properly renders button with provided text', () => {
renderWithTheme(<PrimaryButton text="test" onPress={onPress} />);

expect(screen.getByText('test')).toBeDefined();
});
it('calls provieded onPress method', () => {
render(<PrimaryButton title="test" onPress={onPress} />);
renderWithTheme(<PrimaryButton text="test" onPress={onPress} />);

fireEvent(screen.getByText('test'), 'press');

Expand Down
26 changes: 17 additions & 9 deletions src/components/Button/PrimaryButton.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import React from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';

interface Param {
title: string;
onPress: any;
}
import { useTheme } from '@/hooks';

type Props = React.ComponentProps<typeof Pressable> & {
text: string;
onPress: () => void;
};

const PrimaryButton = ({ text, onPress, ...passThroughProps }: Props) => {
const { themedStyles } = useTheme();

const PrimaryButton = (param: Param) => {
return (
<Pressable style={styles.button} onPress={param.onPress}>
<Text style={styles.text}>{param.title}</Text>
<Pressable
{...passThroughProps}
style={{ ...styles.button, backgroundColor: themedStyles.button }}
onPress={onPress}
>
<Text style={{ ...styles.text, color: themedStyles.buttonText }}>
{text}
</Text>
</Pressable>
);
};
Expand All @@ -20,14 +30,12 @@ const styles = StyleSheet.create({
button: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#5964AB',
width: 291,
height: 44,
borderRadius: 10,
},

text: {
color: '#FFFFFF',
fontSize: 12,
lineHeight: 15,
fontWeight: '500',
Expand Down
File renamed without changes.
File renamed without changes.
5 changes: 2 additions & 3 deletions src/hooks/useTheme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import React, { useContext, useEffect, useState } from 'react';
import { useColorScheme } from 'react-native';
import { darkColors, lightColors } from 'style';

import { isTypeOfTheme, Theme, THEMES } from '@/types/theme';
import { Colors, isTypeOfTheme, Theme, THEMES } from '@/types/theme';

type Props = {
children: React.ReactNode;
};

type Context = {
theme: Theme;
themedStyles: Record<string, string>;
themedStyles: Record<Colors, string>;
switchTheme: () => void;
};

Expand Down Expand Up @@ -45,7 +45,6 @@ export const ThemeProvider = ({ children }: Props) => {

useEffect(() => {
// Load saved theme from storage
console.log('xd');
const getTheme = async () => {
try {
const savedTheme = await AsyncStorage.getItem('theme');
Expand Down
11 changes: 7 additions & 4 deletions src/screens/Components.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet, Text, View } from 'react-native';
import { globalStyles } from 'style';

import { SwitchTheme } from '@/components';
import { PrimaryButton } from '@/components/Button';
import { SwitchTheme } from '@/components/Theme';
import { RootStackParamList } from '@/types/param';

type Props = NativeStackScreenProps<RootStackParamList, 'Components'>;

const Home = ({ navigation }: Props) => {
const { t } = useTranslation('common');

return (
<View style={styles.container}>
<Text>Components screen</Text>
<Text>{t('Hello')}</Text>
<PrimaryButton
title="Wróć do Home Screen"
text="Wróć do Home Screen"
onPress={() => navigation.navigate('Home')}
/>
<SwitchTheme />
<PrimaryButton title="primary button" onPress={() => {}} />
<PrimaryButton text="primary button" onPress={() => {}} />
</View>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/screens/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const Home = ({ navigation }: Props) => {
return (
<View style={styles.container}>
<PrimaryButton
title="Idź do Components Screen"
text="Idź do Components Screen"
onPress={() => navigation.navigate('Components')}
/>
</View>
Expand Down
9 changes: 9 additions & 0 deletions src/tests/renderWithTheme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { render } from '@testing-library/react-native';
import React from 'react';

import { ThemeProvider } from '@/hooks';

const renderWithTheme = (component: React.ReactNode) =>
render(<ThemeProvider>{component}</ThemeProvider>);

export default renderWithTheme;
12 changes: 12 additions & 0 deletions src/types/i18next.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'i18next';

import common from 'public/locales/pl/common.json';

declare module 'i18next' {
interface CustomTypeOptions {
defaultNS: 'common';
resources: {
common: typeof common;
};
}
}
3 changes: 3 additions & 0 deletions src/types/theme.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { lightColors } from 'style';

export const THEMES = ['light', 'dark'] as const;
export type Theme = (typeof THEMES)[number];
export type Colors = keyof typeof lightColors;

export const isTypeOfTheme = (value: any): value is Theme =>
THEMES.includes(value);
15 changes: 13 additions & 2 deletions style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,20 @@ export const globalStyles = StyleSheet.create({
});

export const darkColors = {
primary: 'blue',
button: '#5964AB',
buttonText: '#FFF',
buttonActive: '#1F1F1F',
background: '#2F2F2F',
text: '#FFF',
};

export const lightColors = {
primary: 'green',
button: '#5964AB',
buttonText: '#FFF',
stroke: '#BDC1DD',
icon: '#A7AAC0',
iconBackground: '#EDEEF4',
buttonSettings: '#F5F5F5',
buttonSettingsActive: '#E4E4E4',
text: '#000',
};
Loading
Loading