Skip to content
Draft
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
56 changes: 31 additions & 25 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useAppConfig } from 'hooks/useAppConfig'
import { AuthProvider } from 'hooks/useAuth'
import { CorrectionProvider } from 'hooks/useCorrection'
import { FormProvider } from 'hooks/useForm'
import { LocalizationProvider } from 'hooks/useLocalization'
import { ResponseProvider } from 'hooks/useResponse'
import { StoredFormProvider } from 'hooks/useStoredForm'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
Expand All @@ -23,31 +24,36 @@ export const App = () => {
const { basename } = useAppConfig()
return (
<AuthProvider>
<FormProvider>
<StoredFormProvider>
<ResponseProvider>
<CorrectionProvider>
<Router basename={basename}>
<Navbar />
<Routes>
<Route index element={<Welcome />} />
<Route path="/instructions" element={<Instructions />} />
<Route path="/assessment" element={<Assessment />} />
<Route path="/assessment/done" element={<AssessmentDone />} />
<Route path="/privacy" element={<Privacy />} />
<Route path="/generator" element={<FormGenerator />} />
<Route path="/correction" element={<Correction />} />
<Route path="/export" element={<Export />} />
<Route path="/summary" element={<Summary />} />
<Route path="/login" element={<Login />} />
</Routes>
<RedirectFrom404 />
<AutoLogout />
</Router>
</CorrectionProvider>
</ResponseProvider>
</StoredFormProvider>
</FormProvider>
<LocalizationProvider>
<FormProvider>
<StoredFormProvider>
<ResponseProvider>
<CorrectionProvider>
<Router basename={basename}>
<Navbar />
<Routes>
<Route index element={<Welcome />} />
<Route path="/instructions" element={<Instructions />} />
<Route path="/assessment" element={<Assessment />} />
<Route
path="/assessment/done"
element={<AssessmentDone />}
/>
<Route path="/privacy" element={<Privacy />} />
<Route path="/generator" element={<FormGenerator />} />
<Route path="/correction" element={<Correction />} />
<Route path="/export" element={<Export />} />
<Route path="/summary" element={<Summary />} />
<Route path="/login" element={<Login />} />
</Routes>
<RedirectFrom404 />
<AutoLogout />
</Router>
</CorrectionProvider>
</ResponseProvider>
</StoredFormProvider>
</FormProvider>
</LocalizationProvider>
</AuthProvider>
)
}
64 changes: 25 additions & 39 deletions src/app/pages/Instructions.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,28 @@
import { useLocalizationContext } from 'hooks/useLocalization'
import { Link } from 'react-router-dom'

export const Instructions = () => (
<main className="container mt-4">
<div className="row justify-content-center">
<section className="col-md-10 col-lg-6">
<h1>Instructions</h1>
<ol>
<li>
Please fill out each question to the best of your ability, and
submit the survey before the beginning of the quarter you are
reporting your needs for.
</li>
<li>
Please only consider your organisation's total needs for the quarter
in question that you don't already have covered.
</li>
<li>
It is okay to use estimates, and we understand that conditions &amp;
needs change! We are only looking for a rough understanding of your
needs.
</li>
<li>
If your organisation operates in multiple regions, please fill out
the survey multiple times. Please submit it once per region that you
operate in, so we can keep the needs data separate.
</li>
<li>
If you want to submit an assessment for a different quarter, please
create a separate submission.
</li>
</ol>
export const Instructions = () => {
const { localizeAppString } = useLocalizationContext()
return (
<main className="container mt-4">
<div className="row justify-content-center">
<section className="col-md-10 col-lg-6">
<h1>{localizeAppString('instructions_title')}</h1>
<ol>
<li>{localizeAppString('instructions_1')}</li>
<li>{localizeAppString('instructions_2')}</li>
<li>{localizeAppString('instructions_3')}</li>
<li>{localizeAppString('instructions_4')}</li>
<li>{localizeAppString('instructions_5')}</li>
</ol>

<p className="d-flex justify-content-end">
<Link className="btn btn-primary" to="/assessment">
Continue
</Link>
</p>
</section>
</div>
</main>
)
<p className="d-flex justify-content-end">
<Link className="btn btn-primary" to="/assessment">
{localizeAppString('instructions_button')}
</Link>
</p>
</section>
</div>
</main>
)
}
34 changes: 34 additions & 0 deletions src/components/LanguageSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useLocalizationContext } from 'hooks/useLocalization'
import { FunctionComponent } from 'react'
import { appStrings, LanguageCodeKeys } from 'schema/localizationTypes'

type LangValuesForDropdown = {
code: LanguageCodeKeys
displayName: string
}

export const LanguageSelector: FunctionComponent = () => {
const { languageCode, setLanguageCode } = useLocalizationContext()
const languageValues: LangValuesForDropdown[] = Array.from(
appStrings.entries(),
).map(([langKey, appStringEntry]) => ({
code: langKey,
displayName: appStringEntry.language,
}))

return (
<select
value={languageCode}
className="form-control"
onChange={({ target: { value } }) =>
setLanguageCode(value as LanguageCodeKeys)
}
>
{languageValues.map(({ code, displayName }) => (
<option key={code} value={code}>
{displayName}
</option>
))}
</select>
)
}
4 changes: 4 additions & 0 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ExpiresCountdown } from 'components/ExpiresCountdown'
import { LanguageSelector } from 'components/LanguageSelector'
import { useAppConfig } from 'hooks/useAppConfig'
import { useAuth } from 'hooks/useAuth'
import { useState } from 'react'
Expand Down Expand Up @@ -102,6 +103,9 @@ export const Navbar = () => {
</ul>
<nav className="d-flex">
<ul className="navbar-nav me-auto">
<li className="nav-item">
<LanguageSelector />
</li>
{!isLoggedIn && (
<li className="nav-item">
<Link className="nav-link" to="/login" onClick={close}>
Expand Down
47 changes: 47 additions & 0 deletions src/hooks/useLocalization.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
createContext,
FunctionComponent,
ReactNode,
useCallback,
useContext,
useState,
} from 'react'
import { AppStringValues, LanguageCodeKeys } from 'schema/localizationTypes'
import { getAppStringValue } from 'utils/getAppStringValue'

export const LocalizationContext = createContext<{
languageCode?: LanguageCodeKeys
setLanguageCode: (languageCode: LanguageCodeKeys) => void
localizeAppString: (appStringKey: keyof AppStringValues) => string
}>({
setLanguageCode: () => undefined,
localizeAppString: (_appStringKey) => {
throw new Error("localizeAppString called before it's implemented")
},
})

export const useLocalizationContext = () => useContext(LocalizationContext)

export const LocalizationProvider: FunctionComponent<{
children: ReactNode
}> = ({ children }) => {
const [languageCode, setLanguageCode] = useState<LanguageCodeKeys>('enUS')
const localizeAppString = useCallback(
(appStringKey: keyof AppStringValues) => {
return getAppStringValue(languageCode, appStringKey)
},
[languageCode],
)

return (
<LocalizationContext.Provider
value={{
languageCode,
setLanguageCode,
localizeAppString,
}}
>
{children}
</LocalizationContext.Provider>
)
}
59 changes: 59 additions & 0 deletions src/schema/localizationTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export enum LanguageCode {
enUS = 'en-us',
es = 'es',
uk = 'uk',
}

export type LanguageCodeKeys = keyof typeof LanguageCode

export type AppStringValues = {
instructions_title: string
instructions_1: string
instructions_2: string
instructions_3: string
instructions_4: string
instructions_5: string
instructions_button: string
}

type AppStringEntry = {
language: string
appStringValues: AppStringValues
}

export const appStrings = new Map<LanguageCodeKeys, AppStringEntry>()
appStrings.set('enUS', {
language: 'English',
appStringValues: {
instructions_title: 'Instructions',
instructions_1:
'Please fill out each question to the best of your ability, and submit the survey before the beginning of the quarter you are reporting your needs for.',
instructions_2:
"Please only consider your organisation's total needs for the quarter in question that you don't already have covered.",
instructions_3:
'It is okay to use estimates, and we understand that conditions & needs change! We are only looking for a rough understanding of your needs.',
instructions_4:
'If your organisation operates in multiple regions, please fill out the survey multiple times. Please submit it once per region that you operate in, so we can keep the needs data separate.',
instructions_5:
'If you want to submit an assessment for a different quarter, please create a separate submission.',
instructions_button: 'Continue',
},
})

appStrings.set('uk', {
language: 'Ukranian',
appStringValues: {
instructions_title: 'Інструкції',
instructions_1:
'Будь ласка, заповніть кожне питання найкращим чином, і надішліть опитник до початку кварталу, за який ви повідомляєте свої потреби.',
instructions_2:
'Будь ласка, врахуйте лише загальні потреби вашої організації за питанням, яке ви вже не покрили.',
instructions_3:
'Можна використовувати приблизні значення, і ми розуміємо, що умови та потреби можуть змінюватися! Ми просто шукаємо приблизний розуміння вашої потреби.',
instructions_4:
'Якщо ваша організація діє у кількох регіонах, будь ласка, заповніть опитник кілька разів. Будь ласка, надішліть його один раз на кожний регіон, у якому ви дієте, щоб ми могли відокремити дані про потреби.',
instructions_5:
'Якщо ви хочете надіслати оцінку для іншого кварталу, створіть окрему заявку.',
instructions_button: 'Продовжити',
},
})
25 changes: 25 additions & 0 deletions src/utils/getAppStringValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
appStrings,
AppStringValues,
LanguageCodeKeys,
} from 'schema/localizationTypes'

export const getAppStringValue = (
languageCode: LanguageCodeKeys,
appStringKey: keyof AppStringValues,
): string => {
const appStringEntry = appStrings.get(languageCode)

if (!appStringEntry) {
throw new Error('Language code not supported')
}

const appStringValue =
appStringEntry.appStringValues[appStringKey] ?? appStringEntry

if (!appStringValue) {
throw new Error('Invalid app string key provided')
}

return appStringValue
}
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"noEmit": true,
"jsx": "preserve",
"noFallthroughCasesInSwitch": true,
"importsNotUsedAsValues": "error",
"baseUrl": "src",
"paths": {
"app/*": ["app/*"],
Expand Down