Skip to content

Commit

Permalink
i18n (#189)
Browse files Browse the repository at this point in the history
i18n support based on browser language
Extend translations with count to handle singular and plural texts
Add useTranslatedDocumentTitle hook
Translate Page wrapper components and profile page
Set fullcalendar language
Add translationHelper to translate property value of objects
Remove hardcoded meta description and language
  • Loading branch information
Alf-Melmac committed Dec 9, 2022
1 parent e44abfb commit 76a7720
Show file tree
Hide file tree
Showing 67 changed files with 932 additions and 284 deletions.
2 changes: 0 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
<meta charset="UTF-8"/>
<link rel="icon" href="/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"/>
<meta name="description" content="Dein nächster Schritt in Richtung Zeitvertreib und Gemeinschaft"/>
<meta property="og:locale" content="de_DE"/>

<!--
manifest.json provides metadata used when your web app is installed on a
Expand Down
24 changes: 12 additions & 12 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import {routes} from "./Router";
import {createBrowserRouter, RouterProvider} from "react-router-dom";
import {getThemeOverride} from "./contexts/Theme";
import {NotificationsProvider} from '@mantine/notifications';
import dayjs from 'dayjs';
import de from 'dayjs/locale/de';
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
import {AuthProvider} from './contexts/authentication/AuthProvider';
import {LanguageProvider} from './contexts/language/Language';

export function App(): JSX.Element {
const queryClient = new QueryClient({
Expand All @@ -20,22 +19,23 @@ export function App(): JSX.Element {
const [colorScheme, setColorScheme] = useState<ColorScheme>('dark');
const toggleColorScheme = (value?: ColorScheme) =>
setColorScheme(value || (colorScheme === 'dark' ? 'light' : 'dark'));
dayjs.locale(de);

const router = createBrowserRouter(routes);

return (
<Suspense fallback={<Skeleton/>}>
<QueryClientProvider client={queryClient}>
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
<MantineProvider theme={{colorScheme, ...getThemeOverride()}} withNormalizeCSS withGlobalStyles>
<NotificationsProvider>
<AuthProvider>
<RouterProvider router={router}/>
</AuthProvider>
</NotificationsProvider>
</MantineProvider>
</ColorSchemeProvider>
<LanguageProvider>
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
<MantineProvider theme={{colorScheme, ...getThemeOverride()}} withNormalizeCSS withGlobalStyles>
<NotificationsProvider>
<AuthProvider>
<RouterProvider router={router}/>
</AuthProvider>
</NotificationsProvider>
</MantineProvider>
</ColorSchemeProvider>
</LanguageProvider>
</QueryClientProvider>
</Suspense>
);
Expand Down
151 changes: 151 additions & 0 deletions src/assets/locales/de/translation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
{
"action.clickToCopy": "Klicken zum Kopieren",
"action.delete": "Löschen",
"action.hide": "Ausblenden",
"action.next": "Weiter",
"action.previous": "Vorherige",
"action.save": "Speichern",
"action.saveFields": "Felder speichern",
"admin.utils.deleteUnusedEventTypes": "Remove unused event types",
"admin.utils.listFiles": "Update file list",
"admin.utils.rebuildCalendars": "Rebuild global ICS calendars",
"admin.utils.rebuildEventNotifications": "Rebuild event notifications",
"breadcrumb.calendar": "Event-Kalender",
"breadcrumb.edit": "Bearbeiten",
"breadcrumb.event.new": "Neues Event",
"calendarEvent.tooltip.emptyReserveSlots": "{0} Reservistenplätze frei",
"calendarEvent.tooltip.emptySlots": "Slots frei",
"clickHere": "hier",
"description": "Beschreibung",
"details": "Details",
"documentTitle.admin.utils": "Admin - Utils",
"documentTitle.error.403": "403 - Nicht erlaubt",
"documentTitle.error.404": "404 - Nicht gefunden",
"documentTitle.event.edit": "Bearbeiten - {0}",
"documentTitle.event.new": "Neues Event",
"documentTitle.events": "Eventkalender",
"documentTitle.profile": "Profil",
"documentTitle.profile.own": "Dein Profil",
"dropzone.placeholder.select": "oder auswählen.",
"dropzone.placeholder.upload": "Datei hineinziehen",
"error.notAllowed": "Hier darfst du leider nicht hin",
"error.notFound.description": "Die Seite, die du gerade zu öffnen versuchst, existiert nicht. Hast du dich bei der Adresse vertippt?",
"error.notFound.title": "Irgendetwas stimmt hier nicht...",
"error.notification": "Speichern fehlgeschlagen.{0}",
"error.notification.copying": "Kopieren fehlgeschlagen.",
"event": "Event",
"event.add": "Neues Event anlegen",
"event.creator": "Ersteller",
"event.date": "Datum",
"event.date.placeholder": "Event Datum",
"event.details.add": "Feld hinzufügen",
"event.details.add.default": "Standard \"{0}\" Details hinzufügen",
"event.details.creator": "Mission von",
"event.details.emptySlot": "Freier Slot",
"event.details.hiddenEventWarning": "Dieses Event ist für Mitspieler noch nicht im Kalender sichtbar. Wenn das Slotten beginnen kann, gebe das Event über den Editiermodus frei.",
"event.details.showDescription": "Beschreibung anzeigen",
"event.details.title": "Titel",
"event.evenType.name.edit.nothingFound": "Aktuell können im Editiermodus keine neuen Event-Typen angelegt werden.",
"event.eventType.color": "Event-Typ-Farbe",
"event.eventType.error": "Event-Typen konnten nicht geladen werden. Bitte überprüfe deine Internetverbindung und deinen Login-Status. Du kannst so weiter arbeiten, aber vielleicht nicht speichern. Neuladen der Seite sollte das Problem beheben.",
"event.eventType.name": "Event-Typ-Name",
"event.hidden": "Versteckt",
"event.hidden.tooltip": "Berechtigt alle Interessierten das Event im Kalender zu sehen.",
"event.missionLength": "Missionslänge",
"event.missionType": "Missionstyp",
"event.missionType.other": "Anderes",
"event.missionType.special": "Spezial",
"event.name": "Titel",
"event.name.placeholder": "Event Name",
"event.pictureUrl": "Bild-URL",
"event.reserveParticipating": "Reserve nimmt teil",
"event.save.loading.partOne": "Das Event wird gerade gespeichert. Bei Erfolg wirst du automatisch weitergeleitet. Falls dies nicht funktioniert, kommst du",
"event.save.loading.partThree": "wieder zum Kalender.",
"event.shareable": "Teilen erlauben",
"event.shareable.tooltip": "Ermöglicht es anderen Gruppen diese Event in ihren Kalender einzufügen und darüber Teilnehmer einzutragen.",
"event.startTime": "Startzeit",
"event.startTime.placeholder": "Event Datum",
"eventPage.error.missingEventId": "Invalid state: Event id required",
"events": "Events",
"footer.author": "© 2022 Alf. All rights reserved.",
"footer.legal": "Impressum & Datenschutzerklärung",
"generalInformation": "Allgemeine Informationen",
"hour.plural": "{0} Stunden",
"hour.singular": "{0} Stunde",
"information": "Information",
"input.select.placeholder": "Auswählen...",
"input.unrestricted": "Freitext",
"length.overFourHours": "über 4 Stunden",
"length.threeHours": "3 Stunden",
"length.twoHours": "2 Stunden",
"minute.plural": "{0} Minuten",
"minute.singular": "{0} Minute",
"moreDetails": "Weitere Details",
"nav.calendar": "Kalender",
"nav.login": "Login",
"navigation.backToCalendar": "Zurück zum Kalender",
"no": "no",
"noChanges": "Keine Änderungen",
"notifications.add": "Benachrichtigung hinzufügen",
"notifications.disabled": "Benachrichtigungen deaktiviert.",
"notifications.enabled.plural": "{0} Benachrichtigungen vorgemerkt.",
"notifications.enabled.singular": "{0} Benachrichtigung vorgemerkt.",
"notifications.input.suffix": "vor dem Event",
"notifications.save": "Benachrichtigungen Speichern",
"oClock": "Uhr",
"or": "oder",
"profile.action.showRoles": "Rollen anzeigen",
"profile.externalCalendar.switch": "Kalenderintegration aktivieren",
"profile.externalCalendar.title": "Externer Kalender",
"profile.externalCalendar.tooltip": "Aktiviere hier die Integration mit externen Kalendern, um deine Events z.B. auf dem Handy einfügen zu können.",
"profile.externalCalendar.tutorial.partOne": "Anleitungen zum Einbinden dieser Datei im",
"profile.externalCalendar.tutorial.partTwo": "Wiki",
"profile.info.participatedEvents": "Event-Teilnahmen",
"profile.notifications.save.tooltip": "Anpassungen an den globalen Einstellungen werden nur für Slottungen nach dem Speichern übernommen.",
"profile.notifications.title": "Globale Benachrichtigungseinstellungen",
"profile.notifications.tooltip": "Hier können Benachrichtigungen vor einem Event konfiguriert werden. Benachrichtigungen erhältst du in Form einer Discord-Privatnachricht.",
"profile.steamId": "Steam-ID",
"regex.hours": "\\s?Stunden?",
"regex.minutes": "\\s?Minuten?",
"slot": "Slot",
"slot.add": "Slot hinzufügen",
"slot.blocked": "Gesperrt",
"slot.placeholder": "Slot Name",
"slotlist": "Slotliste",
"slotlist.alt": "Teilnahmeplatzaufzählung",
"slotlist.renumber": "Neu nummerieren",
"slotlist.save": "Slotliste speichern",
"slotlist.upload": "Slotliste hochladen",
"slotlist.upload.description.partOne": "Lade hier deine nicht binarisierte",
"slotlist.upload.description.partThree": "Die Missionsdateien findest du nach dem Speichern unter",
"slotlist.upload.description.partTwo": "hoch, um daraus die Slotliste generieren zu lassen. Die Slotliste überschreibt alle bereits angelegten Plätze, du kannst sie danach aber noch bearbeiten.",
"slotlist.upload.disabled": "Zum Hochladen einer Slotliste muss das Event leer sein",
"slotlist.upload.error.invalidFileType": "Nur .sqm Dateien erlaubt.",
"slotlist.upload.error.multipleFiles": "Nur eine Datei möglich.",
"slotlistEntry.settings": "Regeln",
"slotlistEntry.settings.blocked.description": "Niemand kann sich für diesen Platz anmelden. Optional kann ein Ersatztext angezeigt werden.",
"slotlistEntry.settings.blocked.label": "Blockierung",
"slotlistEntry.settings.blocked.placeholder": "Ersatztext",
"slotlistEntry.settings.header": "Regeln für",
"slotlistEntry.settings.reservation.description": "Nur Mitglieder der ausgewählten Gruppe können sich hier eintragen.",
"slotlistEntry.settings.reservation.error": "Die möglichen Reservierungen konnten nicht geladen werden. Bitte überprüfe deine Internetverbindung. Du kannst ohne Reservierungen fortfahren, aber vielleicht nicht speichern. Durch Navigation auf den vorherigen Schritt und wieder auf die Slotliste zurück wird versucht erneut zu laden.",
"slotlistEntry.settings.reservation.label": "Reservierung",
"slotlistEntry.settings.reservation.placeholder": "Nicht reserviert",
"squad": "Squad",
"squad.add": "Squad hinzufügen",
"squad.placeholder": "Squad Name",
"success.notification": "Gespeichert",
"theme.toggle.dark": "Licht aus",
"theme.toggle.light": "Licht an",
"unsavedChanges": "Ungespeicherte Änderungen",
"userMenu.admin.utils": "Utils",
"userMenu.label.admin": "Administration",
"userMenu.logout": "Logout",
"userMenu.myProfile": "Mein Profil",
"validation.ambiguous": "Uneindeutig",
"validation.event.embedSize": "Event-Information dürfen insgesamt nicht {0} Zeichen überschreiten. Aktuell: {1}",
"validation.hexColor": "Muss ein HEX-Farbcode sein",
"validation.maxLength": "Nicht länger als {0} Zeichen",
"validation.onlyFuture": "Muss in der Zukunft liegen",
"validation.required": "Pflichtfeld"
}
152 changes: 152 additions & 0 deletions src/assets/locales/en/translation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
{
"action.clickToCopy": "EN",
"action.delete": "EN",
"action.hide": "EN",
"action.next": "EN",
"action.previous": "EN",
"action.save": "EN",
"action.saveFields": "EN",
"admin.utils.deleteUnusedEventTypes": "EN",
"admin.utils.listFiles": "EN",
"admin.utils.rebuildCalendars": "EN",
"admin.utils.rebuildEventNotifications": "EN",
"breadcrumb.calendar": "EN",
"breadcrumb.edit": "EN",
"breadcrumb.event.new": "EN",
"calendarEvent.tooltip.emptyReserveSlots": "EN",
"calendarEvent.tooltip.emptySlots": "EN",
"clickHere": "EN",
"description": "EN",
"details": "EN",
"documentTitle.admin.utils": "EN",
"documentTitle.error.403": "EN",
"documentTitle.error.404": "EN",
"documentTitle.event.edit": "EN",
"documentTitle.event.new": "EN",
"documentTitle.events": "EN",
"documentTitle.profile": "EN",
"documentTitle.profile.own": "EN",
"dropzone.placeholder.select": "EN",
"dropzone.placeholder.upload": "EN",
"error.notAllowed": "EN",
"error.notFound.description": "EN",
"error.notFound.title": "EN",
"error.notification": "EN",
"error.notification.copying": "EN",
"event": "EN",
"event.add": "EN",
"event.creator": "EN",
"event.date": "EN",
"event.date.placeholder": "EN",
"event.details.add": "EN",
"event.details.add.default": "EN",
"event.details.creator": "EN",
"event.details.emptySlot": "EN",
"event.details.hiddenEventWarning": "EN",
"event.details.showDescription": "EN",
"event.details.title": "EN",
"event.evenType.name.edit.nothingFound": "EN",
"event.eventType.color": "EN",
"event.eventType.error": "EN",
"event.eventType.name": "EN",
"event.hidden": "EN",
"event.hidden.tooltip": "EN",
"event.missionLength": "EN",
"event.missionType": "EN",
"event.missionType.other": "EN",
"event.missionType.special": "EN",
"event.name": "EN",
"event.name.placeholder": "EN",
"event.pictureUrl": "EN",
"event.reserveParticipating": "EN",
"event.save.loading.partOne": "EN",
"event.save.loading.partThree": "EN",
"event.shareable": "EN",
"event.shareable.tooltip": "EN",
"event.startTime": "EN",
"event.startTime.placeholder": "EN",
"eventPage.error.missingEventId": "EN",
"events": "EN",
"footer.author": "EN",
"footer.legal": "EN",
"generalInformation": "EN",
"hour.plural": "EN",
"hour.singular": "EN",
"information": "EN",
"input.select.placeholder": "EN",
"input.unrestricted": "EN",
"length.overFourHours": "EN",
"length.threeHours": "EN",
"length.twoHours": "EN",
"minute.plural": "EN",
"minute.singular": "EN",
"moreDetails": "EN",
"nav.calendar": "EN",
"nav.login": "EN",
"navigation.backToCalendar": "EN",
"no": "EN",
"noChanges": "EN",
"notifications.add": "EN",
"notifications.disabled": "EN",
"notifications.enabled.plural": "EN",
"notifications.enabled.singular": "EN",
"notifications.input.suffix": "EN",
"notifications.save": "EN",
"oClock": "-",
"or": "EN",
"profile.action.showRoles": "EN",
"profile.externalCalendar.switch": "EN",
"profile.externalCalendar.title": "EN",
"profile.externalCalendar.tooltip": "EN",
"profile.externalCalendar.tutorial.partOne": "EN",
"profile.externalCalendar.tutorial.partThree": "EN",
"profile.externalCalendar.tutorial.partTwo": "EN",
"profile.info.participatedEvents": "EN",
"profile.notifications.save.tooltip": "EN",
"profile.notifications.title": "EN",
"profile.notifications.tooltip": "EN",
"profile.steamId": "EN",
"regex.hours": "EN",
"regex.minutes": "EN",
"slot": "EN",
"slot.add": "EN",
"slot.blocked": "EN",
"slot.placeholder": "EN",
"slotlist": "EN",
"slotlist.alt": "EN",
"slotlist.renumber": "EN",
"slotlist.save": "EN",
"slotlist.upload": "EN",
"slotlist.upload.description.partOne": "EN",
"slotlist.upload.description.partThree": "EN",
"slotlist.upload.description.partTwo": "EN",
"slotlist.upload.disabled": "EN",
"slotlist.upload.error.invalidFileType": "EN",
"slotlist.upload.error.multipleFiles": "EN",
"slotlistEntry.settings": "EN",
"slotlistEntry.settings.blocked.description": "EN",
"slotlistEntry.settings.blocked.label": "EN",
"slotlistEntry.settings.blocked.placeholder": "EN",
"slotlistEntry.settings.header": "EN",
"slotlistEntry.settings.reservation.description": "EN",
"slotlistEntry.settings.reservation.error": "EN",
"slotlistEntry.settings.reservation.label": "EN",
"slotlistEntry.settings.reservation.placeholder": "EN",
"squad": "EN",
"squad.add": "EN",
"squad.placeholder": "EN",
"success.notification": "EN",
"theme.toggle.dark": "EN",
"theme.toggle.light": "EN",
"unsavedChanges": "EN",
"userMenu.admin.utils": "EN",
"userMenu.label.admin": "EN",
"userMenu.logout": "EN",
"userMenu.myProfile": "EN",
"validation.ambiguous": "EN",
"validation.event.embedSize": "EN",
"validation.hexColor": "EN",
"validation.maxLength": "EN",
"validation.onlyFuture": "EN",
"validation.required": "EN"
}

0 comments on commit 76a7720

Please sign in to comment.