Merge branch 'i18n' of gitlab.cip.ifi.lmu.de:jost/UniWorX into i18n

This commit is contained in:
Sarah Vaupel 2019-10-30 10:34:29 +01:00
commit d91a13925d
32 changed files with 244 additions and 21 deletions

View File

@ -1,3 +1,5 @@
import moment from 'moment';
/** /**
* I18n * I18n
* *
@ -13,10 +15,15 @@
export class I18n { export class I18n {
translations = {}; _translations = {};
_datetimeLocale = undefined;
add(id, translation) { add(id, translation) {
this.translations[id] = translation; if (!this._translations[id]) {
this._translations[id] = translation;
} else {
throw new Error('I18N Error: Attempting to set translation multiple times for »' + id + '«!');
}
} }
addMany(manyTranslations) { addMany(manyTranslations) {
@ -24,9 +31,27 @@ export class I18n {
} }
get(id) { get(id) {
if (!this.translations[id]) { if (!this._translations[id]) {
throw new Error('I18N Error: Translation missing for »' + id + '«!'); throw new Error('I18N Error: Translation missing for »' + id + '«!');
} }
return this.translations[id]; return this._translations[id];
}
setDatetimeLocale(locale) {
if (!this._datetimeLocale) {
moment.locale(locale);
this._datetimeLocale = locale;
} else {
throw new Error('I18N Error: Attempting to set datetime locale multiple times!');
}
}
getDatetimeLocale() {
if (!this._datetimeLocale) {
throw new Error('I18N Error: Attempting to access datetime locale when it has not been set!');
}
return this._datetimeLocale;
} }
} }

View File

@ -48,9 +48,6 @@ const DATEPICKER_CONFIG = {
timeMinutes: 0, timeMinutes: 0,
timeSeconds: 0, timeSeconds: 0,
// german settings
// TODO: hardcoded, get from current language / settings
locale: 'de',
weekStart: 1, weekStart: 1,
dateFormat: FORM_DATE_FORMAT_DATE_DT, dateFormat: FORM_DATE_FORMAT_DATE_DT,
timeFormat: FORM_DATE_FORMAT_TIME_DT, timeFormat: FORM_DATE_FORMAT_TIME_DT,
@ -86,6 +83,7 @@ export class Datepicker {
datepickerInstance; datepickerInstance;
_element; _element;
elementType; elementType;
_locale;
constructor(element) { constructor(element) {
if (!element) { if (!element) {
@ -96,6 +94,8 @@ export class Datepicker {
return false; return false;
} }
this._locale = window.App.i18n.getDatetimeLocale();
// initialize datepickerCollections singleton if not already done // initialize datepickerCollections singleton if not already done
if (!Datepicker.datepickerCollections) { if (!Datepicker.datepickerCollections) {
Datepicker.datepickerCollections = new Map(); Datepicker.datepickerCollections = new Map();
@ -134,7 +134,7 @@ export class Datepicker {
} }
// initialize tail.datetime (datepicker) instance and let it do weird stuff with the element value // initialize tail.datetime (datepicker) instance and let it do weird stuff with the element value
this.datepickerInstance = datetime(this._element, { ...datepickerGlobalConfig, ...datepickerConfig }); this.datepickerInstance = datetime(this._element, { ...datepickerGlobalConfig, ...datepickerConfig, locale: this._locale });
// reset date to something sane // reset date to something sane
if (parsedMomentDate) if (parsedMomentDate)

View File

@ -2104,3 +2104,9 @@ MenuGlossary: Begriffsverzeichnis
Applicant: Bewerber Applicant: Bewerber
CourseParticipant: Kursteilnehmer CourseParticipant: Kursteilnehmer
Administrator: Administrator
CsvFormat: CSV-Format
ExerciseSheet: Übungsblatt
DefinitionCourseEvents: Kurstermine
DefinitionCourseNews: Kurs-Aktuelles
Invitations: Einladungen

View File

@ -1802,7 +1802,7 @@ ApplicationRatingSection: Grading
ApplicationRatingSectionSelfTip: You are authorised to edit the application as well as it's grading. ApplicationRatingSectionSelfTip: You are authorised to edit the application as well as it's grading.
AllocationSchoolShort: Department AllocationSchoolShort: Department
Allocation: Central alloction Allocation: Central allocation
AllocationRegisterTo: Registration until AllocationRegisterTo: Registration until
AllocationListTitle: Central allocations AllocationListTitle: Central allocations
@ -2100,3 +2100,9 @@ MenuGlossary: Glossary
Applicant: Applicant Applicant: Applicant
CourseParticipant: Course participant CourseParticipant: Course participant
Administrator: Administrator
CsvFormat: CSV format
ExerciseSheet: Exercise sheet
DefinitionCourseEvents: Course occurrences
DefinitionCourseNews: Course news
Invitations: Invitations

View File

@ -1930,6 +1930,7 @@ siteLayout' headingOverride widget = do
let let
-- See Utils.Frontend.I18n and files in messages/frontend for message definitions -- See Utils.Frontend.I18n and files in messages/frontend for message definitions
frontendI18n = toJSON (mr :: FrontendMessage -> Text) frontendI18n = toJSON (mr :: FrontendMessage -> Text)
frontendDatetimeLocale <- toJSON <$> selectLanguage frontendDatetimeLocales
pc <- widgetToPageContent $ do pc <- widgetToPageContent $ do
-- fonts -- fonts

View File

@ -2,6 +2,7 @@ module Handler.Info where
import Import import Import
import Handler.Utils import Handler.Utils
import Handler.Info.TH
import qualified Data.Map as Map import qualified Data.Map as Map
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
@ -84,7 +85,4 @@ getGlossaryR =
$(widgetFile "glossary") $(widgetFile "glossary")
where where
entries = $(i18nWidgetFiles "glossary") entries = $(i18nWidgetFiles "glossary")
msgMap = Map.fromList msgMap = $(glossaryTerms "glossary")
[ ("applicant" , MsgApplicant )
, ("course-participant", MsgCourseParticipant)
]

23
src/Handler/Info/TH.hs Normal file
View File

@ -0,0 +1,23 @@
module Handler.Info.TH
( glossaryTerms
) where
import Import
import Handler.Utils.I18n
import Language.Haskell.TH
import qualified Data.Char as Char
import qualified Data.Map.Strict as Map
import qualified Data.Text as Text
glossaryTerms :: FilePath -> Q Exp
glossaryTerms basename = do
translationsAvailable <- i18nWidgetFilesAvailable' basename
let terms = Map.mapWithKey (\k _ -> "Msg" <> unPathPiece k) translationsAvailable
[e|Map.fromList $(listE . map (\(int, msg) -> tupE [litE . stringL $ repack int, conE $ mkName msg]) $ Map.toList terms)|]
where
unPathPiece :: Text -> String
unPathPiece = repack . mconcat . map (over _head Char.toUpper) . Text.splitOn "-"

View File

@ -1,10 +1,13 @@
module Handler.Utils.I18n module Handler.Utils.I18n
where ( i18nWidgetFile
, i18nWidgetFilesAvailable, i18nWidgetFilesAvailable', i18nWidgetFiles
) where
import Import import Import
import Language.Haskell.TH import Language.Haskell.TH
import Language.Haskell.TH.Syntax (qRunIO) import Language.Haskell.TH.Syntax (qRunIO)
import qualified Language.Haskell.TH.Syntax as TH
import qualified Data.List as List import qualified Data.List as List
import qualified Data.List.NonEmpty as NonEmpty import qualified Data.List.NonEmpty as NonEmpty
@ -47,8 +50,8 @@ i18nWidgetFile basename = do
] ++ [ clause [wildP] (normalB [e| error "selectLanguage returned an invalid translation" |]) [] ] -- Fallback mostly there so compiler does not complain about non-exhaustive pattern match ] ++ [ clause [wildP] (normalB [e| error "selectLanguage returned an invalid translation" |]) [] ] -- Fallback mostly there so compiler does not complain about non-exhaustive pattern match
] [e|selectLanguage availableTranslations' >>= $(varE ws)|] ] [e|selectLanguage availableTranslations' >>= $(varE ws)|]
i18nWidgetFiles :: FilePath -> Q Exp i18nWidgetFilesAvailable' :: FilePath -> Q (Map Text (NonEmpty Text))
i18nWidgetFiles basename = do i18nWidgetFilesAvailable' basename = do
let i18nDirectory = "templates" </> "i18n" </> basename let i18nDirectory = "templates" </> "i18n" </> basename
availableFiles <- qRunIO $ listDirectory i18nDirectory availableFiles <- qRunIO $ listDirectory i18nDirectory
let fileKinds' = fmap (pack . dropExtension . takeBaseName &&& toTranslation . pack . takeBaseName) availableFiles let fileKinds' = fmap (pack . dropExtension . takeBaseName &&& toTranslation . pack . takeBaseName) availableFiles
@ -58,6 +61,15 @@ i18nWidgetFiles basename = do
availableTranslations' <- iforM fileKinds $ \kind -> maybe (fail $ "" <> i18nDirectory <> " has no translations for " <> unpack kind <> "") return . NonEmpty.nonEmpty availableTranslations' <- iforM fileKinds $ \kind -> maybe (fail $ "" <> i18nDirectory <> " has no translations for " <> unpack kind <> "") return . NonEmpty.nonEmpty
return availableTranslations'
i18nWidgetFilesAvailable :: FilePath -> Q Exp
i18nWidgetFilesAvailable = TH.lift <=< i18nWidgetFilesAvailable'
i18nWidgetFiles :: FilePath -> Q Exp
i18nWidgetFiles basename = do
availableTranslations' <- i18nWidgetFilesAvailable' basename
-- Dispatch to correct language (depending on user settings via `selectLanguage`) at run time -- Dispatch to correct language (depending on user settings via `selectLanguage`) at run time
ws <- newName "ws" -- Name for dispatch function ws <- newName "ws" -- Name for dispatch function
letE letE

View File

@ -1,5 +1,6 @@
module Utils.Frontend.I18n module Utils.Frontend.I18n
( FrontendMessage(..) ( FrontendMessage(..)
, frontendDatetimeLocales
) where ) where
import ClassyPrelude import ClassyPrelude
@ -14,6 +15,9 @@ import Data.Aeson.Types (toJSONKeyText)
import Data.Aeson.TH import Data.Aeson.TH
import qualified Data.Char as Char import qualified Data.Char as Char
import Data.List.NonEmpty (NonEmpty(..))
import Text.Shakespeare.I18N (Lang)
-- | I18n-Messages used in JavaScript-Frontend -- | I18n-Messages used in JavaScript-Frontend
-- --
@ -39,3 +43,7 @@ instance ToJSONKey FrontendMessage where
toJSONKey = toJSONKeyText toPathPiece toJSONKey = toJSONKeyText toPathPiece
instance FromJSONKey FrontendMessage where instance FromJSONKey FrontendMessage where
fromJSONKey = FromJSONKeyTextParser $ parseJSON . String fromJSONKey = FromJSONKeyTextParser $ parseJSON . String
frontendDatetimeLocales :: NonEmpty Lang
frontendDatetimeLocales = "de" :| ["en"]

View File

@ -11,8 +11,6 @@ import qualified Data.List as List
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
import Control.Lens (none)
import Yesod.Core.Types (HandlerData(handlerRequest), YesodRequest(reqLangs)) import Yesod.Core.Types (HandlerData(handlerRequest), YesodRequest(reqLangs))
import qualified Network.Wai.Parse as NWP import qualified Network.Wai.Parse as NWP
@ -41,7 +39,7 @@ selectLanguages avL (l:ls)
, l'' <- matchesFor l' , l'' <- matchesFor l'
, langMatches lParts' l'' , langMatches lParts' l''
] ]
= let now = nonEmpty . filter (\l' -> none (((==) `on` CI.mk) l') ls) $ sortOn (Down . length) found = let now = nonEmpty $ sortOn (Down . length) found
others = selectLanguages avL ls others = selectLanguages avL ls
in maybe id (\now' others' -> NonEmpty.fromList $ toList now' ++ filter (`notElem` toList now') (toList others')) now others in maybe id (\now' others' -> NonEmpty.fromList $ toList now' ++ filter (`notElem` toList now') (toList others')) now others
| otherwise = selectLanguages avL ls | otherwise = selectLanguages avL ls

View File

@ -11,4 +11,4 @@
margin-left: 12px margin-left: 12px
dd + dt, .dd + dt, dd + .dt, .dd + .dt dd + dt, .dd + dt, dd + .dt, .dd + .dt
margin-top: 17px margin-top: 17px

View File

@ -1,6 +1,6 @@
if (window.App) { if (window.App) {
window.App.i18n.addMany(#{frontendI18n}); window.App.i18n.addMany(#{frontendI18n});
// window.App.i18n.setLang(lang); TODO: set language string for datepicker config window.App.i18n.setDatetimeLocale(#{frontendDatetimeLocale});
} else { } else {
throw new Error('I18n JavaScript service is missing!'); throw new Error('I18n JavaScript service is missing!');
} }

View File

@ -0,0 +1,7 @@
$newline never
<dd>
Nutzer, die, um die Funktionstüchtigkeit des Systems zu erhalten, über #
erweiterte Rechte innerhalb eines oder mehrerer Institute verfügen.<br />
Haben vollen Zugriff auf alle Kurse und Funktionen innerhalb ihrer Institute #
und auf einige systemweite Funktionalitäten wie z.B. das Ausstellen von #
Uni2work-internen Kennungen.

View File

@ -0,0 +1,7 @@
$newline never
<dd>
Da verfügbare Kapazitäten in einigen Sorten von Veranstaltung (speziell #
Praktika und Seminare) i.A. nicht hinreichend sind für alle Studierenden die #
in einem bestimmten Semester an einer deartigen Veranstaltung teilnehmen #
wollen oder müssen, werden die verfügbaren Plätze zentral auf die #
Interessenten verteilt.

View File

@ -0,0 +1,4 @@
$newline never
<dd>
Nutzer, die von einem Kursverwalter beauftragt wurden bei der Korrektur von #
Übungsblättern und/oder Prüfungen mitzuwirken.

View File

@ -0,0 +1,14 @@
$newline never
<dd>
<p>
Wenn eine Veranstaltung ein Bewerbungsverfahren hat, werden, statt sich als #
Student direkt für die Veranstaltung anzumelden, nur Bewerbungen an die #
Veranstaltung gestellt.<br />
Es ist dann Aufgabe der Kursverwalter die Bewerbungen zu bewerten und ggf. zu #
akzeptieren.
<p>
Auch Veranstaltungen, die an einer Zentralanmeldung teilnehmen, können ein #
Bewerbungsverfahren haben oder nicht.<br />
In diesem Fall werden immer Bewerbungen (über die Zentralanmeldung) an den #
Kurs gestellt, es werden jedoch die Einstellungen vom kurseigenen #
Bewerbungsverfahren übernommen (z.B. ob die Abgabe von Dateien erlaubt ist).

View File

@ -0,0 +1,8 @@
$newline never
<dt .sec>
Anmeldungsvorlagen
<dd>
Wenn eine Veranstaltung ein Bewerbungsverfahren hat, dass die Abgabe von #
Dateien erlaubt, gibt es für Kursverwalter die Möglichkeit einen Satz von #
Dateien zu publizieren der potentiellen Bewerbern dabei helfen soll #
Bewerbungen anzufertigen, die den Erwartungen der Kursverwalter entsprechen.

View File

@ -0,0 +1,4 @@
$newline never
<dd>
Es lassen sich quasi beliebige Prüfungsformen (Projektabnahmen, mündliche #
Prüfungen, Klausuren, ...) in Uni2work modellieren.

View File

@ -0,0 +1,5 @@
$newline never
<dd>
Nutzer die bei der Organisation einer Veranstaltung mitwirken und hierfür vom #
Nutzer, der den Kurs im System angelegt hat, volle Kontrolle über alle Aspekte #
der Veranstaltung erhalten haben.

View File

@ -0,0 +1,5 @@
$newline never
<dd>
Kursverwalter haben die Möglichkeit direkt in Uni2work zusätzliche #
Kursunterlagen (z.B. Foliensätze oder Programmbeispiele) an ihre #
Kursteilnehmer zu verteilen.

View File

@ -0,0 +1,12 @@
$newline never
<dt .sec>
CSV-Datei
<dt .sec>
CSV-Import
<dt .sec>
CSV-Export
<dd>
Ein weit verbreitetes Format zum semi-strukturierten Datenaustausch zwischen #
unterschiedlichen Systemen.<br />
Kann von den meisten gängigen Tabellenkalkulations-Programmen verarbeitet #
werden.

View File

@ -0,0 +1,5 @@
$newline never
<dd>
Kursverwalter haben die Möglichkeit Termine im Bezug auf ihre Veranstaltung #
(Vorlesungen, Zentralübung, Klausureinsicht, ...) direkt auf der Kursseite zu #
publizieren.

View File

@ -0,0 +1,4 @@
$newline never
<dd>
Kursverwalter haben die Möglichkeit Neuigkeiten und Ankündigungen im Bezug auf #
ihre Veranstaltung direkt auf der Kursseite zu publizieren.

View File

@ -0,0 +1,8 @@
$newline never
<dd>
Kursverwalter (und von ihnen beauftragte Korrektoren) können die Ergebnisse #
einer Prüfung direkt in Uni2work hinterlegen.<br />
Dies dient sowohl der Rückmeldung an den jeweiligen Studierenden als auch #
können Prüfungsämter, mit den notwendigen Berechtigungen, die #
Prüfungsleistungen direkt aus Uni2work mit ihren eigenen Verwaltungssystemen #
synchronisieren.

View File

@ -0,0 +1,8 @@
$newline never
<dd>
Kursverwalter haben die Möglichkeit Übungsaufgaben zu ihren Veranstaltung #
direkt in Uni2work an die Kursteilnehmer zu verteilen.<br />
Gewöhnlicherweise haben Kursteilnehmer dann die Möglichkeit ihre erarbeiteten #
Lösungen in Uni2work hochzuladen (oder die Abgabe in einem externen System, #
z.B. auf Papier zu markieren) sodass Korrektor, Feedback und etwaige #
Bonuspunkte ebenfalls direkt in Uni2work verwaltet werden können.

View File

@ -0,0 +1,13 @@
$newline never
<dd>
Es besteht in Uni2work an vielen Stellen die Möglichkeit andere Benutzer in #
einen Prozess mit einzubeziehen (z.B. als Kursverwalter, Korrektor, #
Mitabgebender, ...). #
In solchen Fällen ist die interne Benutzerkennung des gewünschten Benutzers #
(z.B. die Campus Kennung) im Allgemeinen nicht bekannt.<br />
Deswegen gibt es gewöhnlicherweise die Möglichkeit eine Einladung an eine #
beliebige E-Mail Adresse zu verschicken. #
Wird eine derartige Einladung vom Empfänger angenommen (hierbei wird der #
korrekte Nutzer dann anhand des Logins des Empfängers identifiziert) wird der #
Nutzer ohne weiteres Zutun des Senders in den gewünschten Prozess #
miteinbezogen.

View File

@ -0,0 +1,6 @@
$newline never
<dd>
Das System markiert für jeden Nutzer eine gewisse Anzahl von Kursen als von #
besonderem Interesse.<br />
Die derart markierten Kurse werden für gewöhnlich in einer Leiste am linken #
Rand des Browserfensters angezeigt.

View File

@ -0,0 +1,6 @@
$newline never
<dd>
Diese Berechtigung wird einzelnen Benutzern von einem Administrator für ein #
bestimmtes Institut erteilt.<br />
Diese Benutzer können dann die Teilnehmerlisten aller Kurse des relevanten #
Instituts einsehen um z.B. Vorlesungsumfragen durchzuführen.

View File

@ -0,0 +1,11 @@
$newline never
<dd>
<p>
Modelliert jene Stellen der Universität, die Zugriff auf Prüfungsleistungen #
benötigen um sicherzustellen, dass Studienmodalitäten erfüllt und Leistungen #
korrekt anerkannt werden.<br />
<p>
<i>Teil des Prüfungsamts</i> ist eine Berechtigung die einzelnen Benutzern von #
einem Administrator für ein bestimmtes Institut eingeräumt wird.<br />
Diese Benutzer haben dann u.A. Zugriff auf alle beim relevanten Institut #
erbrachten und in Uni2work hinterlegten Prüfungsleistungen.

View File

@ -0,0 +1,4 @@
$newline never
<dd>
Nutzer denen von einem Administrator das Recht eingeräumt wurde innerhalb #
eines Instituts neue Veranstaltungen anzulegen.

View File

@ -0,0 +1,5 @@
$newline never
<dd>
Das System ist in den meisten Aspekten in Institute unterteilt.<br />
Kurse haben stets eine Institutszugehörigkeit und die meisten #
Benutzerberechtigungen werden Institutsweise vergeben.

View File

@ -0,0 +1,10 @@
$newline never
<dd>
Ist die Abgabe für ein Übungsblatt über ein Uni2work-externes System (z.B. auf #
Papier) vorgesehen so können sich Kursteilnehmer auf der Seite des jeweiligen #
Übungsblattes Pseudonyme generieren lassen.<br />
Wenn die externe Abgabe mit einem derartigen Pseudonym markiert wird haben #
Korrektoren später die Möglichkeit Abgaben in Uni2work anzulegen die zu den #
externen Abgaben korrespondieren.<br />
So kann Feedback und etwaige Bonuspunkte direkt in Uni2work verwaltet werden, #
obwohl die tatsächlich Abgabe extern geschehen ist.