Skip to content

Commit

Permalink
feat: rework app bootstrap and locale initialization (#403)
Browse files Browse the repository at this point in the history
  • Loading branch information
tsyirvo authored Nov 18, 2024
1 parent b04aa88 commit 8a8ac1b
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 106 deletions.
3 changes: 0 additions & 3 deletions src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import Toast from 'react-native-toast-message';

import { bootstrapExternalSdks } from '$core/bootstrapExternalSdks';
import { useAppScreenTracking } from '$core/navigation/hooks/useAppScreenTracking';
import { useAppStateTracking } from '$core/navigation/hooks/useAppStateTracking';
import { Providers } from '$core/providers/Providers';
Expand All @@ -16,8 +15,6 @@ import 'react-native-gesture-handler';

import '../core/i18n';

bootstrapExternalSdks();

const RootLayout = () => {
useCheckNetworkStateOnMount();
useAppStateTracking();
Expand Down
53 changes: 18 additions & 35 deletions src/core/bootstrapExternalSdks.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,33 @@
import { Logger } from '$core/logger';

import { Analytics } from './analytics';
import { Attribution } from './attribution';
import { initDateLocale } from './date';
import { getSupportedDateLocale } from './i18n';
import { getSavedAppLocale } from './i18n/utils/languageDetector';
import { getSupportedLocale } from './i18n';
import { ErrorMonitoring } from './monitoring';
import { Notifications } from './notifications';
import { Purchase } from './purchase';

const initAnalyticsAndAttribution = () => {
Analytics.init()
.then(() => {
Attribution.init().catch((error: unknown) => {
Logger.error({
error,
message: 'Failed to initialize AppsFlyer',
});
});
})
.catch((error: unknown) => {
Logger.error({
error,
message: 'Failed to initialize Amplitude',
});
});
};

const initNotifications = () => {
const initNotifications = (locale: string) => {
Notifications.init();

const locale = getSavedAppLocale();

if (locale) Notifications.setUserLanguage(locale);
Notifications.setUserLanguage(locale);
};

const initDateLib = () => {
const localeToUse = getSupportedDateLocale();

initDateLocale(localeToUse);
const initDateLib = (locale: string) => {
initDateLocale(locale);
};

export const bootstrapExternalSdks = () => {
export const bootstrapExternalSdks = async () => {
// Core services to init first in a specific order
ErrorMonitoring.init();
initAnalyticsAndAttribution();
await Analytics.init();
await Attribution.init();

// Misc
const localeToUse = getSupportedLocale();

// All other core services
initNotifications(localeToUse);
initDateLib(localeToUse);

// Used SDKs
Purchase.init();
initNotifications();
initDateLib();
};
4 changes: 2 additions & 2 deletions src/core/constants/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ const appsflyerDevKey = Env.APPSFLYER_DEV_KEY;
const appsflyerAppId = Env.APPSFLYER_APP_ID;

export const config = {
defaultLocale: 'en',
supportedLocales: ['en', 'fr'],
defaultLocale: 'en' as const,
supportedLocales: ['en', 'fr'] as const,
// App config
env,
isDebug: env === 'development',
Expand Down
2 changes: 1 addition & 1 deletion src/core/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { i18n } from './i18n';
export { changeLanguage } from './utils/languageSwitcher';
export { getSupportedDateLocale } from './utils/detectLocaleToUse';
export { getSupportedLocale } from './utils/detectLocaleToUse';
24 changes: 15 additions & 9 deletions src/core/i18n/utils/detectLocaleToUse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ import * as Localization from 'expo-localization';

import { config } from '$core/constants';

const SELECTED_LOCALIZATION = 0;
const NOT_FOUND_INDEX = -1;
const PRIMARY_LOCALIZATION = 0;
const NOT_FOUND = -1;

const SLICE_START = 0;
const SLICE_END = 2;

const getDeviceRegion = () => {
const locales = Localization.getLocales();
const calendar = Localization.getCalendars();

const primaryLocale = locales[SELECTED_LOCALIZATION];
const primaryCalendar = calendar[SELECTED_LOCALIZATION];
const primaryLocale = locales[PRIMARY_LOCALIZATION];
const primaryCalendar = calendar[PRIMARY_LOCALIZATION];

const region = primaryLocale?.regionCode;
const languageTag = primaryLocale?.languageTag;
Expand All @@ -23,10 +26,10 @@ const getDeviceRegion = () => {
};
};

export const getSupportedDateLocale = () => {
export const getSupportedLocale = () => {
const { languageTag } = getDeviceRegion();

const isSupportedLocale = config.supportedLocales.findIndex(
const supportedLocaleIndex = config.supportedLocales.findIndex(
(supportedLanguage) => {
if (languageTag.includes(supportedLanguage)) {
return true;
Expand All @@ -35,8 +38,11 @@ export const getSupportedDateLocale = () => {
return false;
},
);
const dateLocaleToSet =
isSupportedLocale === NOT_FOUND_INDEX ? null : languageTag;
const localeToSet = supportedLocaleIndex === NOT_FOUND ? null : languageTag;

if (localeToSet) {
return localeToSet.slice(SLICE_START, SLICE_END);
}

return dateLocaleToSet ?? config.defaultLocale;
return config.defaultLocale;
};
24 changes: 7 additions & 17 deletions src/core/i18n/utils/languageDetector.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
import * as Localization from 'expo-localization';
import type { LanguageDetectorModule } from 'i18next';

import { Analytics } from '$core/analytics';
import { config, storageKeys } from '$core/constants';
import { storageKeys } from '$core/constants';
import { AppStorage } from '$core/storage';

import { getSupportedLocale } from './detectLocaleToUse';

export const getSavedAppLocale = () =>
AppStorage.getString(storageKeys.appStorage.locale);

export const setSavedAppLocale = (locale: string) => {
AppStorage.set(storageKeys.appStorage.locale, locale);
};

const detectPhonePrimaryLocale = () => {
const locales = Localization.getLocales();

const primaryLocaleIndex = 0;
const primaryLocale = locales[primaryLocaleIndex];

return primaryLocale?.languageTag;
};

const detectLanguageToUse = () => {
const currentlySelectedLocale = getSavedAppLocale();

Expand All @@ -30,14 +22,12 @@ const detectLanguageToUse = () => {
return currentlySelectedLocale;
}

const phonePrimaryLocale = detectPhonePrimaryLocale();

const selectedLanguage = phonePrimaryLocale ?? config.defaultLocale;
const localeToUse = getSupportedLocale();

Analytics.setUserProperty('language', selectedLanguage);
setSavedAppLocale(selectedLanguage);
Analytics.setUserProperty('language', localeToUse);
setSavedAppLocale(localeToUse);

return selectedLanguage;
return localeToUse;
};

export const languageDetector: LanguageDetectorModule = {
Expand Down
11 changes: 10 additions & 1 deletion src/core/i18n/utils/languageSwitcher.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import i18next from 'i18next';

import { Analytics } from '$core/analytics';
import type { config } from '$core/constants';
import { initDateLocale } from '$core/date';
import { Notifications } from '$core/notifications';
import { Toaster } from '$core/toaster';

import { setSavedAppLocale } from './languageDetector';

type SupportedLanguages = 'en' | 'fr';
import type { UnionFromArray } from '$types';

type SupportedLanguages = UnionFromArray<typeof config.supportedLocales>;

export const changeLanguage = async (language: SupportedLanguages) => {
await i18next.changeLanguage(language, (error, t) => {
Expand All @@ -17,6 +23,9 @@ export const changeLanguage = async (language: SupportedLanguages) => {
}

setSavedAppLocale(language);
Notifications.setUserLanguage(language);
Analytics.setUserProperty('language', language);
initDateLocale(language);

Toaster.show({
text1: t('settings.changeLocale.success'),
Expand Down
4 changes: 2 additions & 2 deletions src/core/monitoring/errorMonitoring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as Sentry from '@sentry/react-native';
import type { Breadcrumb, CaptureContext, SeverityLevel } from '@sentry/types';

import { config } from '$core/constants';
import { Logger } from '$core/logger';

import type { tags } from './constants';

Expand All @@ -23,8 +24,7 @@ class ErrorMonitoringClass {
const isEnabled = config.env !== 'development';

if (!config.sentryDsn) {
// eslint-disable-next-line no-console
console.log('Failed to initialize Sentry - No DSN found');
Logger.dev('Failed to initialize Sentry - No DSN found');

return;
}
Expand Down
4 changes: 2 additions & 2 deletions src/shared/components/splashscreen/Splashscreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import type { ReactNode } from 'react';

import { Box } from '$shared/uiKit/primitives';

import { useLoadAssets } from './hooks/useLoadAssets';
import { useBootstrapApp } from './hooks/useBootstrapApp';

type SplashscreenProps = {
children: ReactNode;
};

export const Splashscreen = ({ children }: SplashscreenProps) => {
const { onLayoutRootView } = useLoadAssets();
const { onLayoutRootView } = useBootstrapApp();

return (
<Box flex={1} onLayout={onLayoutRootView}>
Expand Down
40 changes: 40 additions & 0 deletions src/shared/components/splashscreen/hooks/useBootstrapApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as SplashScreen from 'expo-splash-screen';
import { useCallback } from 'react';

import { bootstrapExternalSdks } from '$core/bootstrapExternalSdks';
import { Logger } from '$core/logger';
import { checkForOtaUpdate } from '$shared/utils/checkForAppUpdates';

SplashScreen.preventAutoHideAsync().catch((error: unknown) => {
Logger.error({
message: 'Failed to persist the SplashScreen',
error,
});
});

export const useBootstrapApp = () => {
const onLayoutRootView = useCallback(() => {
(async () => {
await bootstrapExternalSdks();
await checkForOtaUpdate();
})()
.finally(() => {
SplashScreen.hideAsync().catch((error: unknown) => {
Logger.error({
message: 'Failed to hide the SplashScreen',
error,
});
});
})
.catch((error: unknown) => {
Logger.error({
message: 'Failed to bootstrap app SDKs or check for OTA update',
error,
});
});
}, []);

return {
onLayoutRootView,
};
};
32 changes: 0 additions & 32 deletions src/shared/components/splashscreen/hooks/useLoadAssets.ts

This file was deleted.

5 changes: 3 additions & 2 deletions src/shared/hooks/useWhyDidYouUpdate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import isEqual from 'lodash.isequal';
import { useEffect, useRef } from 'react';

import { Logger } from '$core/logger';

type Props = {
onChangeFound?: (data: {
changesObj: Record<
Expand Down Expand Up @@ -66,8 +68,7 @@ export const useWhyDidYouUpdate = (
if (onChangeFound) {
onChangeFound({ changesObj });
} else {
// eslint-disable-next-line no-console
console.log('[why-did-you-update]', name, {
Logger.dev('[why-did-you-update]', name, {
props: { from: latestProps.current, to: props },
});
}
Expand Down
4 changes: 4 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ export type Primitives =
| symbol
| null
| undefined;

export type UnionFromArray<T extends readonly string[]> = {
[K in T[number]]: K;
}[T[number]];

0 comments on commit 8a8ac1b

Please sign in to comment.