Skip to content

Commit

Permalink
chore: i18n; better bootstrap; redux persist
Browse files Browse the repository at this point in the history
  • Loading branch information
stephane-segning committed Jul 27, 2024
1 parent 239abae commit e7e7f9f
Show file tree
Hide file tree
Showing 21 changed files with 207 additions and 111 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"autoprefixer": "^10.4.19",
"axios": "^1.7.2",
"i18next": "^23.11.4",
"i18next-chained-backend": "^4.6.2",
"i18next-http-backend": "^2.5.2",
"i18next-localstorage-backend": "^4.2.0",
"md-to-pdf": "^5.2.4",
"postcss": "^8.4.38",
"react": "^18.3.1",
Expand Down
6 changes: 6 additions & 0 deletions public/i18n/en/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"action": {
"scan": "Scan"
},
"welcome": "Welcome to the Lynx-Scanner"
}
7 changes: 7 additions & 0 deletions public/i18n/en/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"page": "Welcome to Lynx!",
"description": "Please scan the QR Code for configuration",
"dark": "Dark Mode",
"light": "Light Mode",
"valantine": "Valantine Mode"
}
4 changes: 2 additions & 2 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { Notification } from '@components/notification.tsx';
*/
export function App(): JSX.Element {
return (
<div>
<>
<RouterProvider router={router} />
<Notification />
</div>
</>
);
}
4 changes: 3 additions & 1 deletion src/components/config-scan.button.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useCallback } from 'react';
import { Button } from 'react-daisyui';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';

export default function ConfigScanButton() {
const { t } = useTranslation();
const navigate = useNavigate();
const scanConfigAndPersist = useCallback(
() => navigate('/config/scan'),
Expand All @@ -11,7 +13,7 @@ export default function ConfigScanButton() {

return (
<Button fullWidth onClick={scanConfigAndPersist} color="primary">
Scan
{t('action.scan')}
</Button>
);
}
4 changes: 2 additions & 2 deletions src/components/config.qr-code.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { selectConfigUrl, useAppSelector, useFetchConfigUrl } from '@store';
import { useConfigData, useFetchConfigUrl } from '@store';
import { Loading } from 'react-daisyui';
import QRCode from 'react-qr-code';
import { useEffect } from 'react';

export default function ConfigQrCode() {
const getUrl = useFetchConfigUrl();
const url = useAppSelector(selectConfigUrl);
const url = useConfigData();
useEffect(() => getUrl(), [getUrl]);
return (
<figure>
Expand Down
4 changes: 2 additions & 2 deletions src/components/floating-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ interface ThemeButtonProps {
}

function ThemeButton({ themeName }: ThemeButtonProps) {
const { t } = useTranslation();
const { t: tC } = useTranslation('config');
return (
<button
data-set-theme={'lynx-' + themeName}
data-act-class={'lynx-' + themeName}
>
<BarChart2 />
{t('config.' + themeName)}
{tC(themeName)}
</button>
);
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
removeNotification,
useAppDispatch,
useAppSelector,
} from '../store';
} from '@store';
import { Alert, Button, Toast } from 'react-daisyui';
import { useCallback } from 'react';
import { X } from 'react-feather';
Expand All @@ -19,8 +19,8 @@ export function Notification() {
);
return (
<Toast horizontal="start" vertical="bottom">
{notifications.map((notification, index) => (
<Alert key={index} status="error">
{notifications.map((notification) => (
<Alert key={notification.id} status="error">
<Button
onClick={remove(notification.id)}
size="sm"
Expand Down
8 changes: 5 additions & 3 deletions src/components/scan-list.simple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ export function ScanListSimple({
))}

<Pagination>
<Button onClick={onPrev} className="join-item">
<Button color="primary" onClick={onPrev} className="join-item">
<ArrowLeft />
</Button>
<Button className="join-item">Page {page}</Button>
<Button onClick={onNext} className="join-item">
<Button color="primary" className="join-item">
Page {page}
</Button>
<Button color="primary" onClick={onNext} className="join-item">
<ArrowRight />
</Button>
</Pagination>
Expand Down
60 changes: 41 additions & 19 deletions src/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,47 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import enJSon from './i18n/en.json';
import deJSon from './i18n/de.json';
import ChainedBackend, { ChainedBackendOptions } from 'i18next-chained-backend';
import LocalStorageBackend, {
LocalStorageBackendOptions,
} from 'i18next-localstorage-backend';
import HttpApi, { HttpBackendOptions } from 'i18next-http-backend';
import axios from 'axios';

i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
interpolation: {
escapeValue: false,
},
resources: {
en: {
translation: enJSon,
const axiosInstance = axios.create();

export async function i18nFn() {
await i18n
.use(LanguageDetector)
.use(initReactI18next)
.use(ChainedBackend)
.init<ChainedBackendOptions>({
fallbackLng: 'en',
ns: ['common'],
defaultNS: ['common'],
supportedLngs: ['en', 'de', 'fr'],
interpolation: {
escapeValue: false,
},
de: {
translation: deJSon,
backend: {
backends: [LocalStorageBackend, HttpApi],
backendOptions: [
{
expirationTime: 7 * 24 * 60 * 60 * 1000, // 7 days
} as LocalStorageBackendOptions,
{
loadPath: '/i18n/{{lng}}/{{ns}}.json',
request: async (_, url, __, callback) => {
try {
const { data, status } = await axiosInstance.get(url);
callback(null, { status, data });
} catch (error) {
callback(error, { status: 500, data: {} });
}
},
} as HttpBackendOptions,
],
},
},
});

export default i18n;
});
return i18n;
}
6 changes: 0 additions & 6 deletions src/i18n/de.json

This file was deleted.

13 changes: 0 additions & 13 deletions src/i18n/en.json

This file was deleted.

58 changes: 50 additions & 8 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,53 @@
import './index.scss';
import { isElectron, setupLogging } from '@shared';
import { bootstrap } from '@shared/app.tsx';

if (isElectron) {
setupLogging().then(() => {
bootstrap();
});
} else {
bootstrap();
import * as Sentry from '@sentry/react';
import { i18nFn } from '@i18n';
import ReactDOM from 'react-dom/client';
import React from 'react';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { persistor, store } from '@store';
import { App } from './app.tsx';
import { PersistGate } from 'redux-persist/integration/react';

async function main() {
if (isElectron) {
await setupLogging();
}

const sentryDSN = import.meta.env.VITE_SENTRY_DSN;
if (sentryDSN) {
Sentry.init({
dsn: sentryDSN,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
// Performance Monitoring
tracesSampleRate: 1.0, // Capture 100% of the transactions
// Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled
tracePropagationTargets: ['localhost', /^https:\/\/localhost:\d+\/api/],
// Session Replay
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
}

const i18n = await i18nFn();

const rootElement = document.getElementById('root') as HTMLElement;
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<I18nextProvider i18n={i18n}>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
</I18nextProvider>
</React.StrictMode>
);
}

main();
4 changes: 2 additions & 2 deletions src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { FloatingConfig } from '@components/floating-config.tsx';
export const router = createBrowserRouter([
{
element: (
<div>
<>
<FloatingConfig />
<div className="flex flex-col gap-2 md:gap-4">
<Outlet />
</div>
</div>
</>
),
children: [
{
Expand Down
7 changes: 4 additions & 3 deletions src/screens/app-config.screen.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { lazy } from 'react';
import { t } from 'i18next';
import { Card } from 'react-daisyui';
import { isElectron } from '@shared';
import { useTranslation } from 'react-i18next';

const ConfigQrCode = lazy(() => import('../components/config.qr-code'));
const ConfigScanButton = lazy(() => import('../components/config-scan.button'));
Expand All @@ -10,13 +10,14 @@ const ToListScanButton = lazy(
);

export const Component: React.FC = () => {
const { t } = useTranslation('config');
return (
<div className="flex justify-center h-[100vh] items-center p-4">
<Card className="max-w-sm border-0 sm:border-2 sm:bg-base-200">
{isElectron && <ConfigQrCode />}
<Card.Body>
<Card.Title>{t('config.page')}</Card.Title>
<p>{t('config.description')}</p>
<Card.Title>{t('page')}</Card.Title>
<p>{t('description')}</p>
<Card.Actions>
{!isElectron && <ConfigScanButton />}
{isElectron && <ToListScanButton />}
Expand Down
37 changes: 0 additions & 37 deletions src/shared/app.tsx

This file was deleted.

5 changes: 4 additions & 1 deletion src/store/api/axios-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ export const axiosBaseQuery =
params,
headers,
});
return { data: JSON.parse(result.data) };
if (typeof result.data === 'string') {
return { data: JSON.parse(result.data) };
}
return { data: result.data };
} catch (error) {
if (error instanceof SyntaxError) {
return {
Expand Down
7 changes: 6 additions & 1 deletion src/store/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { useCallback } from 'react';
import { fetchConfigUrl } from './thunks';
import { useAppDispatch } from './types';
import { useAppDispatch, useAppSelector } from './types';
import { selectConfigUrl } from '@store/selectors.ts';

export function useFetchConfigUrl() {
const dispatch = useAppDispatch();
return useCallback(() => {
dispatch(fetchConfigUrl());
}, [dispatch]);
}

export function useConfigData() {
return useAppSelector(selectConfigUrl);
}
Loading

0 comments on commit e7e7f9f

Please sign in to comment.