mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-08 00:43:54 +00:00
Ux language (#378)
* ux: language from menu * feat: add extra support for languages german portuguese spanish * refactor: type improvements
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
import { lazy, useEffect } from 'react';
|
||||
import { Navigate, Route, Routes, useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import useAliases from './common/hooks-query/useAliases';
|
||||
import withData from './features/viewers/ViewWrapper';
|
||||
import { useTranslation } from './translation/TranslationProvider';
|
||||
|
||||
const Editor = lazy(() => import('./features/editors/ProtectedEditor'));
|
||||
const Table = lazy(() => import('./features/table/ProtectedTable'));
|
||||
@@ -37,17 +36,6 @@ export default function AppRouter() {
|
||||
const { data } = useAliases();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { setLanguage } = useTranslation();
|
||||
|
||||
// Set output language
|
||||
useEffect(() => {
|
||||
const langParam = searchParams.get('lang');
|
||||
if (langParam && langParam.length === 2) {
|
||||
setLanguage(searchParams.get('lang'));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchParams]);
|
||||
|
||||
// navigate if is alias route
|
||||
useEffect(() => {
|
||||
|
||||
@@ -106,6 +106,9 @@ export default function AppSettingsModal() {
|
||||
<option value='en'>English</option>
|
||||
<option value='de'>German</option>
|
||||
<option value='no'>Norwegian</option>
|
||||
<option value='pt'>Portuguese</option>
|
||||
<option value='es'>Spanish</option>
|
||||
<option value='sv'>Swedish</option>
|
||||
</Select>
|
||||
</ModalSplitInput>
|
||||
<OntimeModalFooter
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import { ComponentType, useMemo } from 'react';
|
||||
import { TitleBlock } from 'ontime-types';
|
||||
import { useStore } from 'zustand';
|
||||
@@ -11,8 +12,7 @@ import { useViewOptionsStore } from '../../common/stores/viewOptions';
|
||||
export type TitleManager = TitleBlock & { showNow: boolean; showNext: boolean };
|
||||
|
||||
const withData = <P extends object>(Component: ComponentType<P>) => {
|
||||
// eslint-disable-next-line react/display-name -- its ok
|
||||
return (props: P) => {
|
||||
return (props: Partial<P>) => {
|
||||
// persisted app state
|
||||
const isMirrored = useViewOptionsStore((state) => state.mirror);
|
||||
|
||||
|
||||
@@ -1,54 +1,47 @@
|
||||
import React, { createContext, useCallback, useContext, useState } from 'react';
|
||||
import React, { createContext, useCallback, useContext } from 'react';
|
||||
|
||||
import useSettings from '@/common/hooks-query/useSettings';
|
||||
import { langDe } from '@/translation/languages/de';
|
||||
import { langEn } from '@/translation/languages/en';
|
||||
import { langEs } from '@/translation/languages/es';
|
||||
import { langNo } from '@/translation/languages/no';
|
||||
import { langPt } from '@/translation/languages/pt';
|
||||
import { langSv } from '@/translation/languages/sv';
|
||||
|
||||
const translationsList = {
|
||||
en: langEn,
|
||||
es: langEs,
|
||||
de: langDe,
|
||||
no: langNo,
|
||||
pt: langPt,
|
||||
sv: langSv,
|
||||
};
|
||||
|
||||
const ALLOWED_LANGUAGES = Object.keys(translationsList);
|
||||
const DEFAULT_LANGUAGE = 'en';
|
||||
export const TranslationContext = createContext(undefined);
|
||||
|
||||
export const TranslationProvider = ({ children }) => {
|
||||
const [language, setLanguageState] = useState('en');
|
||||
const { data } = useSettings();
|
||||
|
||||
const getLocalizedString = useCallback(
|
||||
(key, lang = language) => {
|
||||
(key, lang = data.language) => {
|
||||
if (key in translationsList[lang]) {
|
||||
return translationsList[lang][key];
|
||||
} else if (lang !== DEFAULT_LANGUAGE) {
|
||||
return getLocalizedString(key, 'en');
|
||||
}
|
||||
},
|
||||
[language],
|
||||
[data.language],
|
||||
);
|
||||
|
||||
const setLanguage = useCallback((language) => {
|
||||
language = language.toLowerCase();
|
||||
if (ALLOWED_LANGUAGES.includes(language)) {
|
||||
setLanguageState(language);
|
||||
console.info(`Language set to ${language}`);
|
||||
} else {
|
||||
console.warn(`Language code ${language} does not exist.`);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const contextValue = {
|
||||
getLocalizedString,
|
||||
setLanguage,
|
||||
};
|
||||
|
||||
return <TranslationContext.Provider value={contextValue}>{children}</TranslationContext.Provider>;
|
||||
};
|
||||
|
||||
export const useTranslation = () => {
|
||||
const { getLocalizedString, setLanguage } = useContext(TranslationContext);
|
||||
return { getLocalizedString, setLanguage };
|
||||
const { getLocalizedString } = useContext(TranslationContext);
|
||||
return { getLocalizedString };
|
||||
};
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import { TranslationObject } from '../Translation.types';
|
||||
|
||||
export const langDe: TranslationObject = {
|
||||
'common.end_time': 'Endzeit',
|
||||
'common.expected_finish': 'Voraussichtliches Ende',
|
||||
'common.now': 'Jetzt',
|
||||
'common.next': 'Nächste',
|
||||
'common.public_message': 'Öffentliche Nachricht',
|
||||
'common.start_time': 'Startzeit',
|
||||
'common.stage_timer': 'Bühnen-Timer',
|
||||
'common.started_at': 'Gestartet am',
|
||||
'common.time_now': 'Aktuelle Zeit',
|
||||
'countdown.ended': 'Veranstaltung endete um',
|
||||
'countdown.running': 'Veranstaltung läuft',
|
||||
'countdown.select_event': 'Wählen Sie eine Veranstaltung aus, um sie zu verfolgen',
|
||||
'countdown.to_start': 'Zeit bis zum Start',
|
||||
'countdown.waiting': 'Warten auf den Veranstaltungsbeginn',
|
||||
};
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { TranslationObject } from '../Translation.types';
|
||||
|
||||
export const langEs: TranslationObject = {
|
||||
'common.end_time': 'Hora de finalización',
|
||||
'common.expected_finish': 'Finalización esperada',
|
||||
'common.now': 'Ahora',
|
||||
'common.next': 'Siguiente',
|
||||
'common.public_message': 'Mensaje público',
|
||||
'common.start_time': 'Hora de inicio',
|
||||
'common.stage_timer': 'Temporizador de presentador',
|
||||
'common.started_at': 'Iniciado en',
|
||||
'common.time_now': 'Hora actual',
|
||||
'countdown.ended': 'Evento finalizado a las',
|
||||
'countdown.running': 'Evento en curso',
|
||||
'countdown.select_event': 'Seleccionar un evento para seguir',
|
||||
'countdown.to_start': 'Tiempo para comenzar',
|
||||
'countdown.waiting': 'Esperando el inicio del evento',
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { TranslationObject } from '../Translation.types';
|
||||
|
||||
export const langPt: TranslationObject = {
|
||||
'common.end_time': 'Hora de término',
|
||||
'common.expected_finish': 'Término esperado',
|
||||
'common.now': 'Agora',
|
||||
'common.next': 'Próximo',
|
||||
'common.public_message': 'Mensagem pública',
|
||||
'common.start_time': 'Hora de início',
|
||||
'common.stage_timer': 'Temporizador do presentador',
|
||||
'common.started_at': 'Iniciado em',
|
||||
'common.time_now': 'Hora atual',
|
||||
'countdown.ended': 'Evento encerrado às',
|
||||
'countdown.running': 'Evento em andamento',
|
||||
'countdown.select_event': 'Selecione um evento para acompanhar',
|
||||
'countdown.to_start': 'Tempo para iniciar',
|
||||
'countdown.waiting': 'Aguardando o início do evento',
|
||||
};
|
||||
Reference in New Issue
Block a user