mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-28 04:39:16 +00:00
🌐(frontend) init i18next
- dynamically load locale files for smaller footprint - have a namespace for each feature. At first I'd figured I'd put each namespace in its correct feature folder but it's kinda cumbersome to manage if we want to link that to i18n management services like crowdin…
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"defaultNamespace": "global",
|
||||
"input": ["src/**/*.{ts,tsx}", "!src/styled-system/**/*", "!src/**/*.d.ts"],
|
||||
"output": "src/locales/$LOCALE/$NAMESPACE.json",
|
||||
"createOldCatalogs": false,
|
||||
"locales": ["en", "fr", "de"]
|
||||
}
|
||||
Generated
+1506
-41
File diff suppressed because it is too large
Load Diff
@@ -7,17 +7,24 @@
|
||||
"dev": "panda codegen && vite",
|
||||
"build": "panda codegen && tsc -b && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"i18n:extract": "i18next -c i18next-parser.config.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "2.3.3",
|
||||
"@livekit/components-styles": "1.0.12",
|
||||
"@pandacss/preset-panda": "0.41.0",
|
||||
"@tanstack/react-query": "5.49.2",
|
||||
"hoofd": "1.7.1",
|
||||
"i18next": "23.12.1",
|
||||
"i18next-browser-languagedetector": "8.0.0",
|
||||
"i18next-parser": "9.0.0",
|
||||
"i18next-resources-to-backend": "1.2.1",
|
||||
"livekit-client": "2.3.1",
|
||||
"react": "18.2.0",
|
||||
"react-aria-components": "1.2.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-i18next": "14.1.3",
|
||||
"wouter": "3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -2,14 +2,19 @@ import '@livekit/components-styles'
|
||||
import '@/styles/index.css'
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useLang } from 'hoofd'
|
||||
import { Route, Switch } from 'wouter'
|
||||
import { Home } from './routes/Home'
|
||||
import { NotFound } from './routes/NotFound'
|
||||
import { RoomRoute } from '@/features/rooms'
|
||||
import './i18n/init'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
function App() {
|
||||
const { i18n } = useTranslation()
|
||||
useLang(i18n.language)
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Switch>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Box } from '@/layout/Box'
|
||||
import { PreJoin, type LocalUserChoices } from '@livekit/components-react'
|
||||
|
||||
@@ -6,12 +7,11 @@ export const Join = ({
|
||||
}: {
|
||||
onSubmit: (choices: LocalUserChoices) => void
|
||||
}) => {
|
||||
const { t } = useTranslation('rooms')
|
||||
|
||||
return (
|
||||
<Box title="Verify your settings before joining" withBackButton>
|
||||
<PreJoin
|
||||
persistUserChoices
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
<Box title={t('join.heading')} withBackButton>
|
||||
<PreJoin persistUserChoices onSubmit={onSubmit} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import i18n from 'i18next'
|
||||
import resourcesToBackend from 'i18next-resources-to-backend'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
import LanguageDetector from 'i18next-browser-languagedetector'
|
||||
const i18nDefaultNamespace = 'global'
|
||||
|
||||
i18n.setDefaultNamespace(i18nDefaultNamespace)
|
||||
i18n
|
||||
.use(
|
||||
resourcesToBackend((language: string, namespace: string) => {
|
||||
return import(`../locales/${language}/${namespace}.json`)
|
||||
})
|
||||
)
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
i18n.init({
|
||||
supportedLngs: ['en', 'fr'],
|
||||
fallbackLng: 'en',
|
||||
ns: i18nDefaultNamespace,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
})
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Box as BoxDiv, H, Link } from '@/primitives'
|
||||
|
||||
export type BoxProps = {
|
||||
@@ -12,6 +13,7 @@ export const Box = ({
|
||||
title = '',
|
||||
withBackButton = false,
|
||||
}: BoxProps) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<BoxDiv asScreen>
|
||||
{!!title && <H lvl={1}>{title}</H>}
|
||||
@@ -19,7 +21,7 @@ export const Box = ({
|
||||
{!!withBackButton && (
|
||||
<p>
|
||||
<Link to="/" size="small">
|
||||
Back to homescreen
|
||||
{t('backToHome')}
|
||||
</Link>
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BoxScreen } from './BoxScreen'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export const ErrorScreen = () => {
|
||||
return (
|
||||
<BoxScreen title="An error occured while loading the page" withBackButton />
|
||||
)
|
||||
const { t } = useTranslation()
|
||||
return <BoxScreen title={t('error.heading')} withBackButton />
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { BoxScreen } from './BoxScreen'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export const ForbiddenScreen = () => {
|
||||
return (
|
||||
<BoxScreen
|
||||
title="You don't have the permission to view this page"
|
||||
withBackButton
|
||||
/>
|
||||
)
|
||||
const { t } = useTranslation()
|
||||
return <BoxScreen title={t('forbidden.heading')} withBackButton />
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { flex } from '@/styled-system/patterns'
|
||||
import { A, Badge, Text } from '@/primitives'
|
||||
import { authUrl, logoutUrl, useUser } from '@/features/auth'
|
||||
import { Link } from 'wouter'
|
||||
|
||||
export const Header = () => {
|
||||
const { t } = useTranslation()
|
||||
const { user, isLoggedIn } = useUser()
|
||||
return (
|
||||
<div
|
||||
@@ -26,16 +29,16 @@ export const Header = () => {
|
||||
>
|
||||
<div>
|
||||
<Text bold variant="h1" margin={false}>
|
||||
Meet
|
||||
<Link to="/">{t('app')}</Link>
|
||||
</Text>
|
||||
</div>
|
||||
<div>
|
||||
{isLoggedIn === false && <A href={authUrl()}>Login</A>}
|
||||
{isLoggedIn === false && <A href={authUrl()}>{t('login')}</A>}
|
||||
{!!user && (
|
||||
<p className={flex({ gap: 1, align: 'center' })}>
|
||||
<Badge>{user.email}</Badge>
|
||||
<A href={logoutUrl()} size="small">
|
||||
Logout
|
||||
{t('logout')}
|
||||
</A>
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BoxScreen } from './BoxScreen'
|
||||
import { Screen } from './Screen'
|
||||
|
||||
export const LoadingScreen = ({ asBox = false }: { asBox?: boolean }) => {
|
||||
const { t } = useTranslation()
|
||||
// show the loading screen only after a little while to prevent flash of texts
|
||||
const [show, setShow] = useState(false)
|
||||
useEffect(() => {
|
||||
@@ -15,7 +17,7 @@ export const LoadingScreen = ({ asBox = false }: { asBox?: boolean }) => {
|
||||
const Container = asBox ? BoxScreen : Screen
|
||||
return (
|
||||
<Container>
|
||||
<p>Loading…</p>
|
||||
<p>{t('loading')}</p>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BoxScreen } from './BoxScreen'
|
||||
|
||||
export const NotFoundScreen = () => {
|
||||
return <BoxScreen title="Page not found" withBackButton />
|
||||
const { t } = useTranslation()
|
||||
return <BoxScreen title={t('notFound.heading')} withBackButton />
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"backToHome": "",
|
||||
"error": {
|
||||
"heading": ""
|
||||
},
|
||||
"forbidden": {
|
||||
"heading": ""
|
||||
},
|
||||
"app": "Meet",
|
||||
"login": "Anmelden",
|
||||
"logout": "",
|
||||
"loading": "",
|
||||
"notFound": {
|
||||
"heading": ""
|
||||
},
|
||||
"homepage": {
|
||||
"heading": "",
|
||||
"intro": "",
|
||||
"createMeeting": "",
|
||||
"login": "",
|
||||
"or": "",
|
||||
"copyMeetingUrl": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"join": {
|
||||
"heading": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"backToHome": "Back to homescreen",
|
||||
"error": {
|
||||
"heading": "An error occured while loading the page"
|
||||
},
|
||||
"forbidden": {
|
||||
"heading": "You don't have the permission to view this page"
|
||||
},
|
||||
"app": "Meet",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"loading": "Loading…",
|
||||
"notFound": {
|
||||
"heading": ""
|
||||
},
|
||||
"homepage": {
|
||||
"heading": "Welcome in Meet",
|
||||
"intro": "What do you want to do? You can either:",
|
||||
"createMeeting": "Create a conference call",
|
||||
"login": "Login to create a conference call",
|
||||
"or": "Or",
|
||||
"copyMeetingUrl": "copy a meeting URL in your browser address bar to join an existing conference call"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"join": {
|
||||
"heading": "Verify your settings before joining"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"backToHome": "Retour à l'accueil",
|
||||
"error": {
|
||||
"heading": "Une erreur est survenue lors du chargement de la page"
|
||||
},
|
||||
"forbidden": {
|
||||
"heading": "Accès interdit"
|
||||
},
|
||||
"app": "Meet",
|
||||
"login": "Se connecter",
|
||||
"logout": "Se déconnecter",
|
||||
"loading": "Chargement…",
|
||||
"notFound": {
|
||||
"heading": "Page introuvable"
|
||||
},
|
||||
"homepage": {
|
||||
"heading": "Bienvenue dans Meet",
|
||||
"intro": "Que voulez vous faire ? Vous pouvez :",
|
||||
"createMeeting": "Créer une conférence",
|
||||
"login": "Vous connecter pour créer une conférence",
|
||||
"or": "Ou",
|
||||
"copyMeetingUrl": "copier une URL de conférence dans votre barre d'adresse pour rejoindre une conférence existante"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"join": {
|
||||
"heading": "Vérifiez vos paramètres"
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,35 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { A, Button, Italic, P, Div, H, Box } from '@/primitives'
|
||||
import { authUrl, useUser } from '@/features/auth'
|
||||
import { navigateToNewRoom } from '@/features/rooms'
|
||||
import { Screen } from '@/layout/Screen'
|
||||
|
||||
export const Home = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'homepage' })
|
||||
const { isLoggedIn } = useUser()
|
||||
return (
|
||||
<Screen>
|
||||
<Box asScreen>
|
||||
<H lvl={1}>Welcome in Meet</H>
|
||||
<P>What do you want to do? You can either:</P>
|
||||
<H lvl={1}>{t('heading')}</H>
|
||||
<P>{t('intro')}</P>
|
||||
<Div marginBottom="gutter">
|
||||
<Box variant="subtle" size="sm">
|
||||
{isLoggedIn ? (
|
||||
<Button variant="primary" onPress={() => navigateToNewRoom()}>
|
||||
Create a conference call
|
||||
{t('createMeeting')}
|
||||
</Button>
|
||||
) : (
|
||||
<p>
|
||||
<A href={authUrl()}>
|
||||
Login to create a conference call
|
||||
</A>
|
||||
<A href={authUrl()}>{t('login')}</A>
|
||||
</p>
|
||||
)}
|
||||
</Box>
|
||||
</Div>
|
||||
<P>
|
||||
<Italic>Or</Italic>
|
||||
<Italic>{t('or')}</Italic>
|
||||
</P>
|
||||
<Box variant="subtle" size="sm">
|
||||
<p>
|
||||
copy a meeting URL in your browser address bar to join an existing
|
||||
conference call
|
||||
</p>
|
||||
<p>{t('copyMeetingUrl')}</p>
|
||||
</Box>
|
||||
</Box>
|
||||
</Screen>
|
||||
|
||||
Reference in New Issue
Block a user