diff options
author | Guillermo Ramos | 2025-03-08 14:07:23 +0100 |
---|---|---|
committer | Guillermo Ramos | 2025-03-08 15:58:02 +0100 |
commit | e93e62ee9f573c494d570f7d5ec5e065e2d8ff73 (patch) | |
tree | 3c72447bdbe8bc529a9639ab1e5630cdf2bd5c63 /front/src | |
parent | 150734bb207fa99c4c74e8e07d79355abf5c7926 (diff) | |
download | hiccup-e93e62ee9f573c494d570f7d5ec5e065e2d8ff73.tar.gz |
Toggle language + Spanish translation
Diffstat (limited to 'front/src')
-rw-r--r-- | front/src/Main.elm | 365 |
1 files changed, 284 insertions, 81 deletions
diff --git a/front/src/Main.elm b/front/src/Main.elm index 1fd3839..6c9b5fe 100644 --- a/front/src/Main.elm +++ b/front/src/Main.elm @@ -59,6 +59,80 @@ amountSep = +-- LOCALE + + +make_t : Language -> String -> String +make_t lang str = + case lang of + EN -> + str + + ES -> + case str of + "Principal: " -> + "Capital: " + + "Interest: " -> + "Interés: " + + "% from total" -> + "% del total" + + "Title..." -> + "Título..." + + "Property price: " -> + "Precio del inmueble: " + + "Initial contribution: " -> + "Contribución inicial: " + + "Interest rate: " -> + "Tipo de interés: " + + "Years: " -> + "Años: " + + "VAT: " -> + "IVA/ITP: " + + "Agent fee: " -> + "Honorarios de agencia: " + + "Simulate" -> + "Simular" + + "Total to pay: " -> + "Total a pagar: " + + "Initial payment: " -> + "Pago inicial: " + + "Property: " -> + "Inmueble: " + + "Financed (mortgage): " -> + "Financiado (hipoteca): " + + "Year" -> + "Año" + + "Month" -> + "Mes" + + "Quota" -> + "Cuota" + + "Pending" -> + "Pendiente" + + _ -> + str + + + +-- "TODO TRANS" -- MAIN @@ -105,18 +179,20 @@ capitalDecoder = (field "interest" float) -capitalSumView : Capital -> Html Msg -capitalSumView { principal, interest } = +capitalSumView : Model -> Capital -> Html Msg +capitalSumView { t } { principal, interest } = let partsTitle = String.concat - [ "Principal: " + [ t "Principal: " , amountToString principal - , "\nInterest: " + , "\n" + , t "Interest: " , amountToString interest , " (" , Round.round 2 (100 * interest / (principal + interest)) - , "% from total)" + , t "% from total" + , ")" ] in span [ class "underline", title partsTitle ] [ amountView (principal + interest) ] @@ -166,29 +242,38 @@ defaultRawSpecs = rawSpecsParser : UQ.Parser RawSpecs rawSpecsParser = - UQ.map8 RawSpecs + let + apply argParser funcParser = + UQ.map2 (<|) funcParser argParser + in + UQ.map RawSpecs (UQ.map (Maybe.withDefault defaultRawSpecs.title) <| UQ.string "title") - (UQ.map (Maybe.withDefault defaultRawSpecs.total) <| UQ.string "total") - (UQ.map (Maybe.withDefault defaultRawSpecs.initial) <| UQ.string "initial") - (UQ.map (Maybe.withDefault defaultRawSpecs.rate) <| UQ.string "rate") - (UQ.map (Maybe.withDefault defaultRawSpecs.i1) <| UQ.string "i1") - (UQ.map (Maybe.withDefault defaultRawSpecs.years) <| UQ.string "years") - (UQ.map (Maybe.withDefault defaultRawSpecs.vat) <| UQ.string "vat") - (UQ.map (Maybe.withDefault defaultRawSpecs.fee) <| UQ.string "fee") - - -rawSpecsToURL : RawSpecs -> String -rawSpecsToURL { title, total, initial, rate, i1, years, vat, fee } = - UB.toQuery <| - [ UB.string "title" title - , UB.string "total" total - , UB.string "initial" initial - , UB.string "rate" rate - , UB.string "i1" i1 - , UB.string "years" years - , UB.string "vat" vat - , UB.string "fee" fee - ] + |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.total) <| UQ.string "total") + |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.initial) <| UQ.string "initial") + |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.rate) <| UQ.string "rate") + |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.i1) <| UQ.string "i1") + |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.years) <| UQ.string "years") + |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.vat) <| UQ.string "vat") + |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.fee) <| UQ.string "fee") + + +rawSpecsToQS : RawSpecs -> List UB.QueryParameter +rawSpecsToQS { title, total, initial, rate, i1, years, vat, fee } = + [ UB.string "title" title + , UB.string "total" total + , UB.string "initial" initial + , UB.string "rate" rate + , UB.string "i1" i1 + , UB.string "years" years + , UB.string "vat" vat + , UB.string "fee" fee + ] + + +modelToUrl : Model -> String +modelToUrl { settings, rawSpecs } = + UB.toQuery + (settingsToQS settings ++ rawSpecsToQS rawSpecs) type alias MortgageSpecs = @@ -212,8 +297,8 @@ parseMortgageSpecs { total, rate, i1, years } = Nothing -mortgageSpecsToURL : MortgageSpecs -> String -mortgageSpecsToURL { principal, i1, years } = +mortgageSpecsToUrl : MortgageSpecs -> String +mortgageSpecsToUrl { principal, i1, years } = UB.absolute [ "api", "simulate" ] [ UB.string "principal" (String.fromFloat principal) , UB.string "i1" (String.fromFloat (i1 / 100)) @@ -224,30 +309,77 @@ mortgageSpecsToURL { principal, i1, years } = runMortgageSim : Model -> MortgageSpecs -> Cmd Msg runMortgageSim m mortgageSpecs = Http.get - { url = mortgageSpecsToURL mortgageSpecs + { url = mortgageSpecsToUrl mortgageSpecs , expect = Http.expectJson (GotMortgageSim m) simDecoder } +type Language + = ES + | EN + + +langToString : Language -> String +langToString lang = + case lang of + ES -> + "ES" + + EN -> + "EN" + + +langFromString : String -> Language +langFromString lang = + case lang of + "ES" -> + ES + + _ -> + EN + + +type alias Settings = + { lang : Language } + + +defaultSettings : Settings +defaultSettings = + { lang = ES } + + +settingsParser : UQ.Parser Settings +settingsParser = + UQ.map Settings + (UQ.map (Maybe.withDefault defaultSettings.lang << Maybe.map langFromString) <| UQ.string "lang") + + +settingsToQS : Settings -> List UB.QueryParameter +settingsToQS { lang } = + [ UB.string "lang" (langToString lang) ] + + type alias Model = - { error : String + { settings : Settings , navKey : Nav.Key + , error : String , rawSpecs : RawSpecs , expandedYears : Set Int , simulation : Maybe ( RawSpecs, MortgageSim ) + , t : String -> String } type Route = NotFound - | Root RawSpecs + | Root ( Settings, RawSpecs ) -routeQuery : Route -> RawSpecs +routeQuery : Route -> ( Settings, RawSpecs ) routeQuery route = case route of NotFound -> - defaultRawSpecs + ( defaultSettings, defaultRawSpecs ) Root query -> query @@ -256,7 +388,7 @@ routeQuery route = routeParser : U.Parser (Route -> a) a routeParser = U.oneOf - [ U.map Root (U.top <?> rawSpecsParser) + [ U.map Root (U.top <?> UQ.map2 Tuple.pair settingsParser rawSpecsParser) ] @@ -267,11 +399,17 @@ toRoute url = init : () -> Url -> Nav.Key -> ( Model, Cmd Msg ) init () url navKey = - ( { navKey = navKey + let + ( settings, rawSpecs ) = + routeQuery (toRoute url) + in + ( { settings = settings + , navKey = navKey , error = "" - , rawSpecs = Debug.log "set rawSpecs" <| routeQuery (toRoute url) + , rawSpecs = rawSpecs , expandedYears = Set.empty , simulation = Nothing + , t = make_t settings.lang } , Cmd.none ) @@ -337,10 +475,15 @@ type SpecField | Fee +type SettingsChange + = ToggleLang + + type Msg = SetUrl UrlRequest | ChangedUrl Url | UpdateSpecs SpecField String + | UpdateSettings SettingsChange | RunMortgageSim MortgageSpecs | GotMortgageSim Model (Result Http.Error MortgageSim) | SetExpandedYears (Set Int) @@ -368,7 +511,7 @@ update msg m = ( m , batch [ runMortgageSim m specs - , Nav.pushUrl m.navKey (rawSpecsToURL m.rawSpecs) + , Nav.pushUrl m.navKey (modelToUrl m) ] ) @@ -379,7 +522,17 @@ update msg m = ( m, Nav.load url ) ChangedUrl url -> - ( { m | rawSpecs = routeQuery (toRoute url) }, Cmd.none ) + let + ( settings, rawSpecs ) = + routeQuery (toRoute url) + in + ( { m + | settings = settings + , rawSpecs = rawSpecs + , t = make_t settings.lang + } + , Cmd.none + ) UpdateSpecs field val -> let @@ -432,16 +585,41 @@ update msg m = SetExpandedYears eyears -> ( { m | expandedYears = eyears }, Cmd.none ) + UpdateSettings change -> + let + settings = + m.settings + + newSettings = + case change of + ToggleLang -> + { settings + | lang = + case settings.lang of + EN -> + ES + + ES -> + EN + } + in + ( m, Nav.pushUrl m.navKey (modelToUrl { m | settings = newSettings }) ) + -- VIEW (THEME) -butAttrs : List (Attribute Msg) -butAttrs = +primaryButAttrs : List (Attribute Msg) +primaryButAttrs = [ class "px-3 rounded-md bg-lime-300 enabled:active:bg-lime-400 border border-lime-600 disabled:opacity-75" ] +secondaryButAttrs : List (Attribute Msg) +secondaryButAttrs = + [ class "px-3 rounded-md text-gray-700 enabled:active:bg-lime-400 border border-2 border-gray-500 disabled:opacity-75" ] + + clickableAttrs : Msg -> List (Attribute Msg) clickableAttrs msg = [ onClick msg, class "text-lime-600", style "cursor" "pointer" ] @@ -514,8 +692,26 @@ amountToString amount = amountStr -specsView : RawSpecs -> Html Msg -specsView rawSpecs = +amountView : Float -> Html Msg +amountView amount = + let + amountStr = + amountToString amount + in + case String.split (String.fromChar amountSep.decimal) amountStr of + int :: float :: [] -> + span [] + [ text int + , text <| String.fromChar amountSep.decimal + , span [ class "text-xs" ] [ text float ] + ] + + _ -> + text amountStr + + +specsView : Model -> Html Msg +specsView { t, settings, rawSpecs } = let { title, total, rate, initial, i1, years, vat, fee } = rawSpecs @@ -532,14 +728,14 @@ specsView rawSpecs = [ div [] [ input [ class "min-w-full mb-2 py-1 px-3 text-xl font-bold lime-100" - , placeholder "Title..." + , placeholder (t "Title...") , value title , onInput (UpdateSpecs Title) ] [] ] , div [ class "flex my-1" ] - [ text "Property price: " + [ text (t "Property price: ") , slider [ Html.Attributes.min "50000", Html.Attributes.max "800000", step "5000" ] (UpdateSpecs TotalValue) total @@ -549,7 +745,7 @@ specsView rawSpecs = ] , div [ class "flex my-1" ] [ div [] - [ text "Initial contribution: " + [ text (t "Initial contribution: ") , txtInput [ class "w-[100px]", Html.Attributes.min "0", Html.Attributes.max total ] (UpdateSpecs Initial) initial @@ -563,14 +759,14 @@ specsView rawSpecs = ] ] , div [ class "my-1" ] - [ text "Interest rate: " + [ text (t "Interest rate: ") , txtInput [ class "w-[80px]", Html.Attributes.min "0", Html.Attributes.max "100" ] (UpdateSpecs I1) i1 , text " % (nominal)" ] , div [ class "flex my-1" ] - [ text "Years: " + [ text (t "Years: ") , slider [ Html.Attributes.min "1", Html.Attributes.max "40", step "1" ] (UpdateSpecs Years) years @@ -578,21 +774,29 @@ specsView rawSpecs = ] , div [ class "flex my-1" ] [ div [] - [ text "VAT: " + [ text (t "VAT: ") , txtInput [ class "w-[55px] mx-1", Html.Attributes.min "0", Html.Attributes.max "50" ] (UpdateSpecs VAT) vat , text "%" ] , div [ class "ml-6" ] - [ text "Agent fee: " + [ text (t "Agent fee: ") , txtInput [ class "w-[55px] mx-1", Html.Attributes.min "0", Html.Attributes.max "10" ] (UpdateSpecs Fee) fee , text "%" ] ] - , button (butAttrs ++ simButAttrs ++ [ class "mt-2" ]) [ text "Simulate" ] + , div [ class "flex justify-between my-1 mt-2" ] + [ button (primaryButAttrs ++ simButAttrs) [ text (t "Simulate") ] + , div [ class "flex" ] + [ button (secondaryButAttrs ++ [ class "mr-1", onClick (UpdateSettings ToggleLang) ]) + [ text <| langToString settings.lang ] + + -- , button (secondaryButAttrs ++ []) [ text "€" ] + ] + ] ] @@ -603,7 +807,16 @@ historyView m quotas = [ "Year", "Month", "Quota", "Pending" ] head = - thead [ class "bg-lime-100" ] [ tr [] (List.map (\t -> th [ class "px-3 py-1 border border-gray-300" ] [ text t ]) titles) ] + thead [ class "bg-lime-100" ] + [ tr [] + (List.map + (\txt -> + th [ class "px-3 py-1 border border-gray-300" ] + [ text <| m.t txt ] + ) + titles + ) + ] in div [ class "pt-4 flex flex-col items-center" ] [ table [ class "border border-collapse bg-gray-50 border-gray-400" ] @@ -616,24 +829,6 @@ historyView m quotas = ] -amountView : Float -> Html Msg -amountView amount = - let - amountStr = - amountToString amount - in - case String.split (String.fromChar amountSep.decimal) amountStr of - int :: float :: [] -> - span [] - [ text int - , text <| String.fromChar amountSep.decimal - , span [ class "text-xs" ] [ text float ] - ] - - _ -> - text amountStr - - periodToYear : Int -> Int periodToYear period = ((period - 1) // 12) + 1 @@ -678,7 +873,7 @@ quotaView m { period, payed, pending_principal } = ] , text (String.fromInt period) - , capitalSumView payed + , capitalSumView m payed , amountView pending_principal ] ) @@ -689,7 +884,7 @@ quotaView m { period, payed, pending_principal } = [ text "" , text (String.fromInt period) - , capitalSumView payed + , capitalSumView m payed , amountView pending_principal ] ) @@ -701,6 +896,9 @@ quotaView m { period, payed, pending_principal } = simView : Model -> ( RawSpecs, MortgageSim ) -> Html Msg simView m ( rawSpecs, { history, topay, payed } ) = let + t = + m.t + parseFloat = Maybe.withDefault 0 << String.toFloat @@ -719,21 +917,26 @@ simView m ( rawSpecs, { history, topay, payed } ) = div [] [ hr [ class "my-5" ] [] , pre [ class "leading-none" ] - [ text "Total to pay: " + [ text <| t "Total to pay: " , amountView (topay.principal + topay.interest + initial + vat + fee) - , text "\n├ Initial payment: " + , text "\n├ " + , text <| t "Initial payment: " , amountView (initial + vat + fee) - , text "\n│ ├ Property: " + , text "\n│ ├ " + , text <| t "Property: " , amountView initial - , text "\n│ ├ Agent fee: " + , text "\n│ ├ " + , text <| t "Agent fee: " , amountView fee - , text "\n│ └ VAT: " + , text "\n│ └ " + , text <| t "VAT: " , amountView vat - , text "\n└ Financed (mortgage): " - , capitalSumView topay + , text "\n└ " + , text <| t "Financed (mortgage): " + , capitalSumView m topay ] - -- , div [] [ text "payed: ", capitalSumView payed ] + -- , div [] [ text "payed: ", capitalSumView m payed ] , historyView m history ] @@ -744,7 +947,7 @@ view m = , body = [ div [ class "flex flex-col max-w-lg mx-auto items-center mt-2 p-3 border-2 rounded-md border-gray-500 bg-gray-100" ] [ div [ class "min-w-full" ] - [ specsView m.rawSpecs + [ specsView m , case m.simulation of Nothing -> text "" |