mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 06:59:09 +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 { 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 useAliases from './common/hooks-query/useAliases';
|
||||||
import withData from './features/viewers/ViewWrapper';
|
import withData from './features/viewers/ViewWrapper';
|
||||||
import { useTranslation } from './translation/TranslationProvider';
|
|
||||||
|
|
||||||
const Editor = lazy(() => import('./features/editors/ProtectedEditor'));
|
const Editor = lazy(() => import('./features/editors/ProtectedEditor'));
|
||||||
const Table = lazy(() => import('./features/table/ProtectedTable'));
|
const Table = lazy(() => import('./features/table/ProtectedTable'));
|
||||||
@@ -37,17 +36,6 @@ export default function AppRouter() {
|
|||||||
const { data } = useAliases();
|
const { data } = useAliases();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
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
|
// navigate if is alias route
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -106,6 +106,9 @@ export default function AppSettingsModal() {
|
|||||||
<option value='en'>English</option>
|
<option value='en'>English</option>
|
||||||
<option value='de'>German</option>
|
<option value='de'>German</option>
|
||||||
<option value='no'>Norwegian</option>
|
<option value='no'>Norwegian</option>
|
||||||
|
<option value='pt'>Portuguese</option>
|
||||||
|
<option value='es'>Spanish</option>
|
||||||
|
<option value='sv'>Swedish</option>
|
||||||
</Select>
|
</Select>
|
||||||
</ModalSplitInput>
|
</ModalSplitInput>
|
||||||
<OntimeModalFooter
|
<OntimeModalFooter
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable react/display-name */
|
||||||
import { ComponentType, useMemo } from 'react';
|
import { ComponentType, useMemo } from 'react';
|
||||||
import { TitleBlock } from 'ontime-types';
|
import { TitleBlock } from 'ontime-types';
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
@@ -11,8 +12,7 @@ import { useViewOptionsStore } from '../../common/stores/viewOptions';
|
|||||||
export type TitleManager = TitleBlock & { showNow: boolean; showNext: boolean };
|
export type TitleManager = TitleBlock & { showNow: boolean; showNext: boolean };
|
||||||
|
|
||||||
const withData = <P extends object>(Component: ComponentType<P>) => {
|
const withData = <P extends object>(Component: ComponentType<P>) => {
|
||||||
// eslint-disable-next-line react/display-name -- its ok
|
return (props: Partial<P>) => {
|
||||||
return (props: P) => {
|
|
||||||
// persisted app state
|
// persisted app state
|
||||||
const isMirrored = useViewOptionsStore((state) => state.mirror);
|
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 { langDe } from '@/translation/languages/de';
|
||||||
import { langEn } from '@/translation/languages/en';
|
import { langEn } from '@/translation/languages/en';
|
||||||
|
import { langEs } from '@/translation/languages/es';
|
||||||
import { langNo } from '@/translation/languages/no';
|
import { langNo } from '@/translation/languages/no';
|
||||||
|
import { langPt } from '@/translation/languages/pt';
|
||||||
import { langSv } from '@/translation/languages/sv';
|
import { langSv } from '@/translation/languages/sv';
|
||||||
|
|
||||||
const translationsList = {
|
const translationsList = {
|
||||||
en: langEn,
|
en: langEn,
|
||||||
|
es: langEs,
|
||||||
de: langDe,
|
de: langDe,
|
||||||
no: langNo,
|
no: langNo,
|
||||||
|
pt: langPt,
|
||||||
sv: langSv,
|
sv: langSv,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ALLOWED_LANGUAGES = Object.keys(translationsList);
|
|
||||||
const DEFAULT_LANGUAGE = 'en';
|
const DEFAULT_LANGUAGE = 'en';
|
||||||
export const TranslationContext = createContext(undefined);
|
export const TranslationContext = createContext(undefined);
|
||||||
|
|
||||||
export const TranslationProvider = ({ children }) => {
|
export const TranslationProvider = ({ children }) => {
|
||||||
const [language, setLanguageState] = useState('en');
|
const { data } = useSettings();
|
||||||
|
|
||||||
const getLocalizedString = useCallback(
|
const getLocalizedString = useCallback(
|
||||||
(key, lang = language) => {
|
(key, lang = data.language) => {
|
||||||
if (key in translationsList[lang]) {
|
if (key in translationsList[lang]) {
|
||||||
return translationsList[lang][key];
|
return translationsList[lang][key];
|
||||||
} else if (lang !== DEFAULT_LANGUAGE) {
|
} else if (lang !== DEFAULT_LANGUAGE) {
|
||||||
return getLocalizedString(key, 'en');
|
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 = {
|
const contextValue = {
|
||||||
getLocalizedString,
|
getLocalizedString,
|
||||||
setLanguage,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return <TranslationContext.Provider value={contextValue}>{children}</TranslationContext.Provider>;
|
return <TranslationContext.Provider value={contextValue}>{children}</TranslationContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTranslation = () => {
|
export const useTranslation = () => {
|
||||||
const { getLocalizedString, setLanguage } = useContext(TranslationContext);
|
const { getLocalizedString } = useContext(TranslationContext);
|
||||||
return { getLocalizedString, setLanguage };
|
return { getLocalizedString };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
import { TranslationObject } from '../Translation.types';
|
import { TranslationObject } from '../Translation.types';
|
||||||
|
|
||||||
export const langDe: TranslationObject = {
|
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',
|
'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