From 322a3e837ccc52884dbd6c4bd0b358cea690541a Mon Sep 17 00:00:00 2001
From: Eike Kettner <eike.kettner@posteo.de>
Date: Sat, 27 Mar 2021 22:00:50 +0100
Subject: [PATCH] Prepare for selecting languages

UI language is stored in user settings for authenticated users;
otherwise is only stored in the current model (not persisted).
---
 modules/webapp/src/main/elm/App/Data.elm      | 18 ++++
 modules/webapp/src/main/elm/App/Update.elm    |  9 ++
 modules/webapp/src/main/elm/App/View2.elm     | 52 ++++++++++-
 .../src/main/elm/Comp/UiSettingsForm.elm      | 44 ++++++++++
 .../webapp/src/main/elm/Data/UiSettings.elm   |  9 ++
 modules/webapp/src/main/elm/Messages.elm      | 87 +++++++++++++++++++
 modules/webapp/src/main/elm/Messages/App.elm  | 22 +++++
 .../src/main/elm/Messages/FixedDropdown.elm   | 22 +++++
 .../src/main/elm/Messages/LoginPage.elm       | 57 ++++++++++++
 modules/webapp/src/main/elm/UiLanguage.elm    | 16 ++++
 10 files changed, 335 insertions(+), 1 deletion(-)
 create mode 100644 modules/webapp/src/main/elm/Messages.elm
 create mode 100644 modules/webapp/src/main/elm/Messages/App.elm
 create mode 100644 modules/webapp/src/main/elm/Messages/FixedDropdown.elm
 create mode 100644 modules/webapp/src/main/elm/Messages/LoginPage.elm
 create mode 100644 modules/webapp/src/main/elm/UiLanguage.elm

diff --git a/modules/webapp/src/main/elm/App/Data.elm b/modules/webapp/src/main/elm/App/Data.elm
index b047f250..4c3a4c6c 100644
--- a/modules/webapp/src/main/elm/App/Data.elm
+++ b/modules/webapp/src/main/elm/App/Data.elm
@@ -2,6 +2,7 @@ module App.Data exposing
     ( Model
     , Msg(..)
     , defaultPage
+    , getUiLanguage
     , init
     )
 
@@ -24,6 +25,7 @@ import Page.Queue.Data
 import Page.Register.Data
 import Page.Upload.Data
 import Page.UserSettings.Data
+import UiLanguage exposing (UiLanguage)
 import Url exposing (Url)
 
 
@@ -48,6 +50,8 @@ type alias Model =
     , uiSettings : UiSettings
     , sidebarVisible : Bool
     , anonymousTheme : UiTheme
+    , anonymousUiLang : UiLanguage
+    , langMenuOpen : Bool
     }
 
 
@@ -97,6 +101,8 @@ init key url flags_ settings =
       , uiSettings = settings
       , sidebarVisible = settings.sideMenuVisible
       , anonymousTheme = Data.UiTheme.Light
+      , anonymousUiLang = UiLanguage.English
+      , langMenuOpen = False
       }
     , Cmd.batch
         [ Cmd.map UserSettingsMsg uc
@@ -152,8 +158,20 @@ type Msg
     | GetUiSettings UiSettings
     | ToggleSidebar
     | ToggleDarkMode
+    | ToggleLangMenu
+    | SetLanguage UiLanguage
 
 
 defaultPage : Flags -> Page
 defaultPage flags =
     HomePage
+
+
+getUiLanguage : Model -> UiLanguage
+getUiLanguage model =
+    case model.flags.account of
+        Just _ ->
+            model.uiSettings.uiLang
+
+        Nothing ->
+            model.anonymousUiLang
diff --git a/modules/webapp/src/main/elm/App/Update.elm b/modules/webapp/src/main/elm/App/Update.elm
index 63e3aceb..4752f4b0 100644
--- a/modules/webapp/src/main/elm/App/Update.elm
+++ b/modules/webapp/src/main/elm/App/Update.elm
@@ -84,6 +84,15 @@ updateWithSub msg model =
                     , Sub.none
                     )
 
+        ToggleLangMenu ->
+            ( { model | langMenuOpen = not model.langMenuOpen }
+            , Cmd.none
+            , Sub.none
+            )
+
+        SetLanguage lang ->
+            ( { model | anonymousUiLang = lang, langMenuOpen = False }, Cmd.none, Sub.none )
+
         HomeMsg lm ->
             updateHome lm model
 
diff --git a/modules/webapp/src/main/elm/App/View2.elm b/modules/webapp/src/main/elm/App/View2.elm
index 7c907437..fe32be78 100644
--- a/modules/webapp/src/main/elm/App/View2.elm
+++ b/modules/webapp/src/main/elm/App/View2.elm
@@ -7,6 +7,7 @@ import Data.Flags
 import Html exposing (..)
 import Html.Attributes exposing (..)
 import Html.Events exposing (onClick)
+import Messages
 import Page exposing (Page(..))
 import Page.CollectiveSettings.View2 as CollectiveSettings
 import Page.Home.Data
@@ -20,6 +21,7 @@ import Page.Register.View2 as Register
 import Page.Upload.View2 as Upload
 import Page.UserSettings.View2 as UserSettings
 import Styles as S
+import UiLanguage
 
 
 view : Model -> List (Html Msg)
@@ -64,13 +66,18 @@ topNavUser auth model =
 
 topNavAnon : Model -> Html Msg
 topNavAnon model =
+    let
+        texts =
+            Messages.get <| App.Data.getUiLanguage model
+    in
     nav
         [ id "top-nav"
         , class styleTopNav
         ]
         [ headerNavItem model
         , div [ class "flex flex-grow justify-end" ]
-            [ a
+            [ langMenu model
+            , a
                 [ href "#"
                 , onClick ToggleDarkMode
                 , class dropdownLink
@@ -100,6 +107,10 @@ headerNavItem model =
 
 mainContent : Model -> Html Msg
 mainContent model =
+    let
+        texts =
+            Messages.get <| App.Data.getUiLanguage model
+    in
     div
         [ id "main"
         , class styleMain
@@ -151,6 +162,45 @@ styleMain =
     "mt-12 flex md:flex-row flex-col w-full h-screen-12 overflow-y-hidden bg-white dark:bg-bluegray-800 text-gray-800 dark:text-bluegray-300 antialiased"
 
 
+langMenu : Model -> Html Msg
+langMenu model =
+    let
+        texts =
+            Messages.get <| App.Data.getUiLanguage model
+
+        langItem lang =
+            let
+                langMsg =
+                    Messages.get lang
+            in
+            a
+                [ classList
+                    [ ( dropdownItem, True )
+                    , ( "bg-gray-200 dark:bg-bluegray-700", lang == texts.lang )
+                    ]
+                , onClick (SetLanguage lang)
+                , href "#"
+                ]
+                [ i [ langMsg |> .flagIcon |> class ] []
+                , span [ class "ml-2" ] [ text langMsg.label ]
+                ]
+    in
+    div [ class "relative" ]
+        [ a
+            [ class dropdownLink
+            , onClick ToggleLangMenu
+            , href "#"
+            ]
+            [ i [ class texts.flagIcon ] []
+            ]
+        , div
+            [ class dropdownMenu
+            , classList [ ( "hidden", not model.langMenuOpen ) ]
+            ]
+            (List.map langItem UiLanguage.all)
+        ]
+
+
 dataMenu : AuthResult -> Model -> Html Msg
 dataMenu _ model =
     div [ class "relative" ]
diff --git a/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm b/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm
index c9f0a010..3f086604 100644
--- a/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm
+++ b/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm
@@ -11,11 +11,13 @@ import Api.Model.TagList exposing (TagList)
 import Comp.BasicSizeField
 import Comp.ColorTagger
 import Comp.FieldListSelect
+import Comp.FixedDropdown
 import Comp.IntField
 import Comp.MenuBar as MB
 import Comp.Tabs
 import Data.BasicSize exposing (BasicSize)
 import Data.Color exposing (Color)
+import Data.DropdownStyle as DS
 import Data.Fields exposing (Field)
 import Data.Flags exposing (Flags)
 import Data.ItemTemplate as IT exposing (ItemTemplate)
@@ -26,8 +28,10 @@ import Html.Attributes exposing (..)
 import Html.Events exposing (onClick, onInput)
 import Http
 import Markdown
+import Messages
 import Set exposing (Set)
 import Styles as S
+import UiLanguage exposing (UiLanguage)
 import Util.Maybe
 import Util.Tag
 
@@ -56,6 +60,8 @@ type alias Model =
     , searchStatsVisible : Bool
     , sideMenuVisible : Bool
     , powerSearchEnabled : Bool
+    , uiLangModel : Comp.FixedDropdown.Model UiLanguage
+    , uiLang : UiLanguage
     , openTabs : Set String
     }
 
@@ -148,6 +154,10 @@ init flags settings =
       , searchStatsVisible = settings.searchStatsVisible
       , sideMenuVisible = settings.sideMenuVisible
       , powerSearchEnabled = settings.powerSearchEnabled
+      , uiLang = settings.uiLang
+      , uiLangModel =
+            List.map langItem UiLanguage.all
+                |> Comp.FixedDropdown.init
       , openTabs = Set.empty
       }
     , Api.getTags flags "" GetTagsResp
@@ -174,6 +184,15 @@ type Msg
     | ToggleAkkordionTab String
     | ToggleSideMenuVisible
     | TogglePowerSearch
+    | UiLangMsg (Comp.FixedDropdown.Msg UiLanguage)
+
+
+langItem : UiLanguage -> Comp.FixedDropdown.Item UiLanguage
+langItem lang =
+    { id = lang
+    , display = Messages.get lang |> .label
+    , icon = Just (Messages.get lang |> .flagIcon)
+    }
 
 
 
@@ -445,6 +464,22 @@ update sett msg model =
             , Just { sett | powerSearchEnabled = next }
             )
 
+        UiLangMsg lm ->
+            let
+                ( m, sel ) =
+                    Comp.FixedDropdown.update lm model.uiLangModel
+
+                newLang =
+                    Maybe.withDefault model.uiLang sel
+            in
+            ( { model | uiLangModel = m, uiLang = newLang }
+            , if newLang == model.uiLang then
+                Nothing
+
+              else
+                Just { sett | uiLang = newLang }
+            )
+
 
 
 --- View2
@@ -494,6 +529,15 @@ settingFormTabs flags _ model =
                         , value = model.sideMenuVisible
                         }
                 ]
+            , div [ class "mb-4" ]
+                [ label [ class S.inputLabel ] [ text "UI Language" ]
+                , Html.map UiLangMsg
+                    (Comp.FixedDropdown.viewStyled2 DS.mainStyle
+                        False
+                        (Just <| langItem model.uiLang)
+                        model.uiLangModel
+                    )
+                ]
             ]
       }
     , { title = "Item Search"
diff --git a/modules/webapp/src/main/elm/Data/UiSettings.elm b/modules/webapp/src/main/elm/Data/UiSettings.elm
index 988eb2d4..3bcbb0c0 100644
--- a/modules/webapp/src/main/elm/Data/UiSettings.elm
+++ b/modules/webapp/src/main/elm/Data/UiSettings.elm
@@ -32,6 +32,8 @@ import Data.UiTheme exposing (UiTheme)
 import Dict exposing (Dict)
 import Html exposing (Attribute)
 import Html.Attributes as HA
+import Messages
+import UiLanguage exposing (UiLanguage)
 
 
 {-| Settings for the web ui. All fields should be optional, since it
@@ -63,6 +65,7 @@ type alias StoredUiSettings =
     , uiTheme : Maybe String
     , sideMenuVisible : Bool
     , powerSearchEnabled : Bool
+    , uiLang : Maybe String
     }
 
 
@@ -94,6 +97,7 @@ type alias UiSettings =
     , uiTheme : UiTheme
     , sideMenuVisible : Bool
     , powerSearchEnabled : Bool
+    , uiLang : UiLanguage
     }
 
 
@@ -165,6 +169,7 @@ defaults =
     , uiTheme = Data.UiTheme.Light
     , sideMenuVisible = True
     , powerSearchEnabled = False
+    , uiLang = UiLanguage.English
     }
 
 
@@ -217,6 +222,9 @@ merge given fallback =
             |> Maybe.withDefault fallback.uiTheme
     , sideMenuVisible = given.sideMenuVisible
     , powerSearchEnabled = given.powerSearchEnabled
+    , uiLang =
+        Maybe.map Messages.fromIso2 given.uiLang
+            |> Maybe.withDefault UiLanguage.English
     }
 
 
@@ -254,6 +262,7 @@ toStoredUiSettings settings =
     , uiTheme = Just (Data.UiTheme.toString settings.uiTheme)
     , sideMenuVisible = settings.sideMenuVisible
     , powerSearchEnabled = settings.powerSearchEnabled
+    , uiLang = Just <| Messages.toIso2 settings.uiLang
     }
 
 
diff --git a/modules/webapp/src/main/elm/Messages.elm b/modules/webapp/src/main/elm/Messages.elm
new file mode 100644
index 00000000..0a07608e
--- /dev/null
+++ b/modules/webapp/src/main/elm/Messages.elm
@@ -0,0 +1,87 @@
+module Messages exposing
+    ( Messages
+    , fromIso2
+    , get
+    , toIso2
+    )
+
+import Messages.App
+import Messages.LoginPage
+import UiLanguage exposing (UiLanguage(..))
+
+
+{-| The messages record contains all strings used in the application.
+-}
+type alias Messages =
+    { lang : UiLanguage
+    , iso2 : String
+    , label : String
+    , flagIcon : String
+    , app : Messages.App.Texts
+    , login : Messages.LoginPage.Texts
+    }
+
+
+get : UiLanguage -> Messages
+get lang =
+    case lang of
+        English ->
+            gb
+
+        German ->
+            de
+
+
+{-| Get a ISO-3166-1 code of the given lanugage.
+-}
+toIso2 : UiLanguage -> String
+toIso2 lang =
+    get lang |> .iso2
+
+
+{-| Return the UiLanguage from given iso2 code. If the iso2 code is not
+known, return Nothing.
+-}
+readIso2 : String -> Maybe UiLanguage
+readIso2 iso =
+    let
+        isIso lang =
+            iso == toIso2 lang
+    in
+    List.filter isIso UiLanguage.all
+        |> List.head
+
+
+{-| return the language from the given iso2 code. if the iso2 code is
+not known, return English as a default.
+-}
+fromIso2 : String -> UiLanguage
+fromIso2 iso =
+    readIso2 iso
+        |> Maybe.withDefault English
+
+
+
+--- Messages Definitions
+
+
+gb : Messages
+gb =
+    { lang = English
+    , iso2 = "gb"
+    , label = "English"
+    , flagIcon = "flag-icon flag-icon-gb"
+    , app = Messages.App.gb
+    , login = Messages.LoginPage.gb
+    }
+
+
+de : Messages
+de =
+    { lang = German
+    , iso2 = "de"
+    , label = "Deutsch"
+    , flagIcon = "flag-icon flag-icon-de"
+    , app = Messages.App.de
+    , login = Messages.LoginPage.de
+    }
diff --git a/modules/webapp/src/main/elm/Messages/App.elm b/modules/webapp/src/main/elm/Messages/App.elm
new file mode 100644
index 00000000..1c3af83b
--- /dev/null
+++ b/modules/webapp/src/main/elm/Messages/App.elm
@@ -0,0 +1,22 @@
+module Messages.App exposing
+    ( Texts
+    , de
+    , gb
+    )
+
+
+type alias Texts =
+    { login : String
+    }
+
+
+gb : Texts
+gb =
+    { login = "Login"
+    }
+
+
+de : Texts
+de =
+    { login = "Anmelden"
+    }
diff --git a/modules/webapp/src/main/elm/Messages/FixedDropdown.elm b/modules/webapp/src/main/elm/Messages/FixedDropdown.elm
new file mode 100644
index 00000000..32f7e983
--- /dev/null
+++ b/modules/webapp/src/main/elm/Messages/FixedDropdown.elm
@@ -0,0 +1,22 @@
+module Messages.FixedDropdown exposing
+    ( Texts
+    , de
+    , gb
+    )
+
+
+type alias Texts =
+    { select : String
+    }
+
+
+gb : Texts
+gb =
+    { select = "Select…"
+    }
+
+
+de : Texts
+de =
+    { select = "Auswahl…"
+    }
diff --git a/modules/webapp/src/main/elm/Messages/LoginPage.elm b/modules/webapp/src/main/elm/Messages/LoginPage.elm
new file mode 100644
index 00000000..befd838a
--- /dev/null
+++ b/modules/webapp/src/main/elm/Messages/LoginPage.elm
@@ -0,0 +1,57 @@
+module Messages.LoginPage exposing
+    ( Texts
+    , de
+    , fr
+    , gb
+    )
+
+
+type alias Texts =
+    { username : String
+    , password : String
+    , loginPlaceholder : String
+    , passwordPlaceholder : String
+    , loginButton : String
+    , loginSuccessful : String
+    , noAccount : String
+    , signupLink : String
+    }
+
+
+gb : Texts
+gb =
+    { username = "Username"
+    , password = "Password"
+    , loginPlaceholder = "Login"
+    , passwordPlaceholder = "Password"
+    , loginButton = "Login"
+    , loginSuccessful = "Login successful"
+    , noAccount = "No account?"
+    , signupLink = "Sign up!"
+    }
+
+
+de : Texts
+de =
+    { username = "Benutzer"
+    , password = "Passwort"
+    , loginPlaceholder = "Benutzer"
+    , passwordPlaceholder = "Passwort"
+    , loginButton = "Anmelden"
+    , loginSuccessful = "Anmeldung erfolgreich"
+    , noAccount = "Kein Konto?"
+    , signupLink = "Hier registrieren!"
+    }
+
+
+fr : Texts
+fr =
+    { username = "Identifiant"
+    , password = "Mot de passe"
+    , loginPlaceholder = "Utilisateur"
+    , passwordPlaceholder = "Mot de passe"
+    , loginButton = "Connexion"
+    , loginSuccessful = "Identification réussie"
+    , noAccount = "Pas de compte ?"
+    , signupLink = "S'inscrire"
+    }
diff --git a/modules/webapp/src/main/elm/UiLanguage.elm b/modules/webapp/src/main/elm/UiLanguage.elm
new file mode 100644
index 00000000..05a46f61
--- /dev/null
+++ b/modules/webapp/src/main/elm/UiLanguage.elm
@@ -0,0 +1,16 @@
+module UiLanguage exposing
+    ( UiLanguage(..)
+    , all
+    )
+
+
+type UiLanguage
+    = English
+    | German
+
+
+all : List UiLanguage
+all =
+    [ English
+    , German
+    ]