Skip to content

Commit c18a8c6

Browse files
authored
Merge pull request #504 from LiamMorrow/translations
2 parents 03db992 + d73eecc commit c18a8c6

49 files changed

Lines changed: 1285 additions & 844 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ babel.config.js
88
expo-env.d.ts
99
dist
1010
node_modules
11+
types/dom.slim.d.ts

app/app/(tabs)/settings/app-configuration.tsx

Lines changed: 1 addition & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
11
import FullHeightScrollView from '@/components/presentation/full-height-scroll-view';
22
import ListSwitch from '@/components/presentation/list-switch';
3-
import SelectButton, {
4-
SelectButtonOption,
5-
} from '@/components/presentation/select-button';
63
import ThemeChooser from '@/components/presentation/theme-chooser';
74
import { RootState, useAppSelector } from '@/store';
85
import {
96
setColorSchemeSeed,
10-
setFirstDayOfWeek,
117
setShowBodyweight,
128
setShowTips,
13-
setUseImperialUnits,
149
} from '@/store/settings';
15-
import { formatDate, getDateOnDay } from '@/utils/format-date';
16-
import { DayOfWeek } from '@js-joda/core';
1710
import { T, useTranslate } from '@tolgee/react';
1811
import { Stack } from 'expo-router';
1912
import { List } from 'react-native-paper';
@@ -24,73 +17,12 @@ export default function AppConfiguration() {
2417
const settings = useAppSelector((state: RootState) => state.settings);
2518
const dispatch = useDispatch();
2619

27-
const daysOfWeekOptions: SelectButtonOption<DayOfWeek>[] = [
28-
{
29-
value: DayOfWeek.SUNDAY,
30-
label: formatDate(getDateOnDay(DayOfWeek.SUNDAY), {
31-
weekday: 'long',
32-
}),
33-
},
34-
{
35-
value: DayOfWeek.MONDAY,
36-
label: formatDate(getDateOnDay(DayOfWeek.MONDAY), {
37-
weekday: 'long',
38-
}),
39-
},
40-
{
41-
value: DayOfWeek.TUESDAY,
42-
label: formatDate(getDateOnDay(DayOfWeek.TUESDAY), {
43-
weekday: 'long',
44-
}),
45-
},
46-
{
47-
value: DayOfWeek.WEDNESDAY,
48-
label: formatDate(getDateOnDay(DayOfWeek.WEDNESDAY), {
49-
weekday: 'long',
50-
}),
51-
},
52-
{
53-
value: DayOfWeek.THURSDAY,
54-
label: formatDate(getDateOnDay(DayOfWeek.THURSDAY), {
55-
weekday: 'long',
56-
}),
57-
},
58-
{
59-
value: DayOfWeek.FRIDAY,
60-
label: formatDate(getDateOnDay(DayOfWeek.FRIDAY), {
61-
weekday: 'long',
62-
}),
63-
},
64-
{
65-
value: DayOfWeek.SATURDAY,
66-
label: formatDate(getDateOnDay(DayOfWeek.SATURDAY), {
67-
weekday: 'long',
68-
}),
69-
},
70-
];
71-
7220
return (
7321
<FullHeightScrollView>
7422
<Stack.Screen options={{ title: t('AppConfiguration') }} />
7523
<List.Section>
7624
<ListSwitch
77-
headline={<T keyName="UseImperialUnits" />}
78-
supportingText={<T keyName="UseImperialUnitsSubtitle" />}
79-
value={settings.useImperialUnits}
80-
onValueChange={(value) => dispatch(setUseImperialUnits(value))}
81-
/>
82-
<List.Item
83-
title={t('SetFirstDayOfWeek')}
84-
description={t('SetFirstDayOfWeekSubtitle')}
85-
right={() => (
86-
<SelectButton
87-
value={settings.firstDayOfWeek}
88-
options={daysOfWeekOptions}
89-
onChange={(value) => dispatch(setFirstDayOfWeek(value))}
90-
/>
91-
)}
92-
/>
93-
<ListSwitch
25+
testID="setShowBodyweight"
9426
headline={<T keyName="ShowBodyweight" />}
9527
supportingText={<T keyName="ShowBodyweightSubtitle" />}
9628
value={settings.showBodyweight}

app/app/(tabs)/settings/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,19 @@ export default function Settings() {
3434
left={(props) => <List.Icon icon={'directionsRun'} {...props} />}
3535
></List.Item>
3636
<List.Item
37+
testID="appConfiguration"
3738
onPress={() => push('/(tabs)/settings/app-configuration')}
3839
title={t('AppConfiguration')}
3940
description={t('AppConfigurationSubtitle')}
4041
left={(props) => <List.Icon icon={'settings'} {...props} />}
4142
></List.Item>
43+
<List.Item
44+
testID="localization"
45+
onPress={() => push('/(tabs)/settings/localization')}
46+
title={t('Localisation')}
47+
description={t('LocalisationSubtitle')}
48+
left={(props) => <List.Icon icon={'language'} {...props} />}
49+
></List.Item>
4250

4351
<List.Item
4452
onPress={() => push('/(tabs)/settings/notifications')}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import FullHeightScrollView from '@/components/presentation/full-height-scroll-view';
2+
import ListSwitch from '@/components/presentation/list-switch';
3+
import SelectButton, {
4+
SelectButtonOption,
5+
} from '@/components/presentation/select-button';
6+
import { supportedLanguages } from '@/services/tolgee';
7+
import { RootState, useAppSelector } from '@/store';
8+
import {
9+
setFirstDayOfWeek,
10+
setPreferredLanguage,
11+
setUseImperialUnits,
12+
} from '@/store/settings';
13+
import { formatDate, getDateOnDay } from '@/utils/format-date';
14+
import { DayOfWeek } from '@js-joda/core';
15+
import { T, useTranslate } from '@tolgee/react';
16+
import { Stack } from 'expo-router';
17+
import { useMemo } from 'react';
18+
import { List } from 'react-native-paper';
19+
import { useDispatch } from 'react-redux';
20+
21+
const daysOfWeekOptions: SelectButtonOption<DayOfWeek>[] = [
22+
{
23+
value: DayOfWeek.SUNDAY,
24+
label: formatDate(getDateOnDay(DayOfWeek.SUNDAY), {
25+
weekday: 'long',
26+
}),
27+
},
28+
{
29+
value: DayOfWeek.MONDAY,
30+
label: formatDate(getDateOnDay(DayOfWeek.MONDAY), {
31+
weekday: 'long',
32+
}),
33+
},
34+
{
35+
value: DayOfWeek.TUESDAY,
36+
label: formatDate(getDateOnDay(DayOfWeek.TUESDAY), {
37+
weekday: 'long',
38+
}),
39+
},
40+
{
41+
value: DayOfWeek.WEDNESDAY,
42+
label: formatDate(getDateOnDay(DayOfWeek.WEDNESDAY), {
43+
weekday: 'long',
44+
}),
45+
},
46+
{
47+
value: DayOfWeek.THURSDAY,
48+
label: formatDate(getDateOnDay(DayOfWeek.THURSDAY), {
49+
weekday: 'long',
50+
}),
51+
},
52+
{
53+
value: DayOfWeek.FRIDAY,
54+
label: formatDate(getDateOnDay(DayOfWeek.FRIDAY), {
55+
weekday: 'long',
56+
}),
57+
},
58+
{
59+
value: DayOfWeek.SATURDAY,
60+
label: formatDate(getDateOnDay(DayOfWeek.SATURDAY), {
61+
weekday: 'long',
62+
}),
63+
},
64+
];
65+
66+
export default function Localization() {
67+
const { t } = useTranslate();
68+
const settings = useAppSelector((state: RootState) => state.settings);
69+
const dispatch = useDispatch();
70+
71+
const languageOptions: SelectButtonOption<string | undefined>[] = useMemo(
72+
() => [
73+
{
74+
value: undefined,
75+
label: t('System default'),
76+
},
77+
...supportedLanguages.map((x) => ({ value: x.code, label: x.label })),
78+
],
79+
[t],
80+
);
81+
82+
return (
83+
<FullHeightScrollView>
84+
<Stack.Screen options={{ title: t('Localisation') }} />
85+
<List.Section>
86+
<ListSwitch
87+
testID="setUseImperialUnits"
88+
headline={<T keyName="UseImperialUnits" />}
89+
supportingText={<T keyName="UseImperialUnitsSubtitle" />}
90+
value={settings.useImperialUnits}
91+
onValueChange={(value) => dispatch(setUseImperialUnits(value))}
92+
/>
93+
<List.Item
94+
title={t('SetFirstDayOfWeek')}
95+
description={t('SetFirstDayOfWeekSubtitle')}
96+
right={() => (
97+
<SelectButton
98+
testID="setFirstDayOfWeek"
99+
value={settings.firstDayOfWeek}
100+
options={daysOfWeekOptions}
101+
onChange={(value) => dispatch(setFirstDayOfWeek(value))}
102+
/>
103+
)}
104+
/>
105+
<List.Item
106+
title={t('Set language')}
107+
description={t('Set your preferred language')}
108+
right={() => (
109+
<SelectButton
110+
testID="setPreferredLanguage"
111+
value={settings.preferredLanguage}
112+
options={languageOptions}
113+
onChange={(value) => dispatch(setPreferredLanguage(value))}
114+
/>
115+
)}
116+
/>
117+
</List.Section>
118+
</FullHeightScrollView>
119+
);
120+
}

app/app/_layout.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { Stack } from 'expo-router';
22
import { AppThemeProvider } from '@/hooks/useAppTheme';
3-
import { TolgeeProvider } from '@tolgee/react';
4-
import { LogBox, Platform, Text, useColorScheme } from 'react-native';
3+
import { LogBox, Platform, useColorScheme } from 'react-native';
54
import { SafeAreaProvider } from 'react-native-safe-area-context';
65
import { Provider } from 'react-redux';
76
import { store } from '@/store';
8-
import AppStateProvider from '@/components/smart/app-state-provider';
9-
import { tolgee } from '@/services/tolgee';
7+
import { AppStateProvider } from '@/components/smart/app-state-provider';
108
import SnackbarProvider from '@/components/smart/snackbar-provider';
119
import { GestureHandlerRootView } from 'react-native-gesture-handler';
1210
import '@/utils/date-locale';
1311
import { KeyboardProvider } from 'react-native-keyboard-controller';
1412
import * as Sentry from '@sentry/react-native';
13+
import ServicesProvider from '@/components/smart/services-provider';
1514

1615
Sentry.init({
1716
dsn: 'https://86576716425e1558b5e8622ba65d4544@o4505937515249664.ingest.us.sentry.io/4509717493383168',
@@ -39,7 +38,7 @@ export default Sentry.wrap(function RootLayout() {
3938
<KeyboardProvider>
4039
<Provider store={store}>
4140
<SafeAreaProvider>
42-
<TolgeeProvider tolgee={tolgee} fallback={<Text>Loading...</Text>}>
41+
<ServicesProvider>
4342
<AppThemeProvider>
4443
<Stack
4544
layout={(e) => (
@@ -63,7 +62,7 @@ export default Sentry.wrap(function RootLayout() {
6362
}}
6463
/>
6564
</AppThemeProvider>
66-
</TolgeeProvider>
65+
</ServicesProvider>
6766
</SafeAreaProvider>
6867
</Provider>
6968
</KeyboardProvider>

app/components/presentation/ms-icon-source.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import { msExpandCircleDown } from '@material-symbols-react-native/outlined-400/
8989
import { msExpandCircleUp } from '@material-symbols-react-native/outlined-400/msExpandCircleUp';
9090
import { msSearch } from '@material-symbols-react-native/outlined-400/msSearch';
9191
import { msDirectionsRun } from '@material-symbols-react-native/outlined-400/msDirectionsRun';
92+
import { msLanguage } from '@material-symbols-react-native/outlined-400/msLanguage';
9293

9394
// Importing these icons using the below methods causes android app to crash
9495
// import { msAdd, msArrowDownward } from '@material-symbols-react-native/outlined-400';
@@ -105,6 +106,7 @@ const MaterialSymbols = {
105106
contentCopy: msContentCopy,
106107
copyAll: msCopyAll,
107108
assignmentAdd: msAssignmentAdd,
109+
language: msLanguage,
108110
delete: msDelete,
109111
edit: msEdit,
110112
error: msError,

app/components/smart/app-state-provider.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useAppSelector } from '@/store';
44
import { ReactNode } from 'react';
55
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
66

7-
export default function AppStateProvider(props: { children: ReactNode }) {
7+
export function AppStateProvider({ children }: { children: ReactNode }) {
88
const waitingOn = useAppSelector(
99
(s) =>
1010
getLoadMessage(s.app, 'app settings') ||
@@ -16,17 +16,23 @@ export default function AppStateProvider(props: { children: ReactNode }) {
1616
);
1717
const { colors } = useAppTheme();
1818

19-
return !waitingOn ? (
20-
props.children
21-
) : (
22-
<Animated.View
23-
entering={FadeIn}
24-
exiting={FadeOut}
25-
style={{ flex: 1, backgroundColor: colors.surface, alignItems: 'center' }}
26-
>
27-
<Loader loadingText={waitingOn} />
28-
</Animated.View>
29-
);
19+
if (waitingOn) {
20+
return (
21+
<Animated.View
22+
entering={FadeIn}
23+
exiting={FadeOut}
24+
style={{
25+
flex: 1,
26+
backgroundColor: colors.surface,
27+
alignItems: 'center',
28+
}}
29+
>
30+
<Loader loadingText={waitingOn} />
31+
</Animated.View>
32+
);
33+
}
34+
35+
return children;
3036
}
3137

3238
function getLoadMessage(state: { isHydrated: boolean }, type: string) {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { resolveServices, Services } from '@/services';
2+
import { RootState } from '@/store';
3+
import { Store } from '@reduxjs/toolkit';
4+
import { TolgeeProvider } from '@tolgee/react';
5+
import { createContext, ReactNode, useContext, useMemo } from 'react';
6+
import { useStore } from 'react-redux';
7+
8+
// Create context for services
9+
const ServicesContext = createContext<Services | null>(null);
10+
export default function ServicesProvider(props: { children: ReactNode }) {
11+
const store = useStore();
12+
const services = useMemo(
13+
() => resolveServices(store as Store<RootState>),
14+
[store],
15+
);
16+
17+
return (
18+
<ServicesContext.Provider value={services}>
19+
<TolgeeProvider tolgee={services.tolgee}>{props.children}</TolgeeProvider>
20+
</ServicesContext.Provider>
21+
);
22+
}
23+
export function useServices() {
24+
const ctx = useContext(ServicesContext);
25+
if (!ctx) throw new Error('useServices must be used within AppStateProvider');
26+
return ctx;
27+
}

0 commit comments

Comments
 (0)