diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bd6e8ab7..18c47278 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,8 +3,6 @@ name: Release on: push: branches: [ main ] - pull_request: - branches: [ main ] jobs: # Separate build job for JavaScript diff --git a/CHANGELOG.md b/CHANGELOG.md index 646533bc..7e573d14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), @@ -6,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 4.3.0 + +### Added + +* Add localization support + ## 4.2.0 ### Fixed diff --git a/build.fsx b/build.fsx index 7cdf1095..82608f0a 100644 --- a/build.fsx +++ b/build.fsx @@ -122,7 +122,7 @@ module Stages = ) } - let donetRestore = + let dotnetRestore = stage "Restore .NET dependencies" { run "dotnet restore" } let npmInstall = stage "NPM install" { run "npm install" } @@ -131,7 +131,7 @@ module Stages = stage "NPM install" { whenCmd { name "--local" - description "Build using local pacakges from fable repository" + description "Build using local packages from fable repository" } run @@ -260,7 +260,7 @@ module Stages = pipeline "WatchApp" { Stages.clean - Stages.donetRestore + Stages.dotnetRestore Stages.npmInstall // We don't need to call unlink because npm install always reset // the dependencies so they are not linked anymore and will be linked @@ -278,7 +278,7 @@ pipeline "WatchApp" { pipeline "BuildApp" { Stages.clean - Stages.donetRestore + Stages.dotnetRestore Stages.npmInstall Stages.npmLink Stages.copyModules @@ -321,14 +321,14 @@ pipeline "Release" { whenBranch "main" Stages.clean - Stages.donetRestore + Stages.dotnetRestore Stages.npmInstall Stages.copyModules Stages.buildApp Stages.updatePreludeREPLVersion - // When releasing locall we use the gh-pages CLI tool + // When releasing local we use the gh-pages CLI tool // When releasing on CI we use the corresponding Github Action stage "Push to gh-pages (local)" { whenNot { @@ -343,7 +343,7 @@ pipeline "Release" { pipeline "BuildLib" { Stages.clean - Stages.donetRestore + Stages.dotnetRestore Stages.npmInstall Stages.copyModules diff --git a/paket.dependencies b/paket.dependencies index 69666c0f..2457303c 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -17,9 +17,10 @@ nuget Feliz prerelease nuget Feliz.Bulma nuget Feliz.Bulma.Tooltip nuget Fable.Browser.Css -nuget Fable.Browser.Dom -nuget Fable.Browser.Event +nuget Fable.Browser.Dom == 2.4.4 +nuget Fable.Browser.Event == 1.4.5 nuget Fable.Browser.MediaQueryList +nuget Fable.Browser.Navigator == 2.0.0 # REPL Lib @@ -32,8 +33,8 @@ nuget Fable.Browser.MediaQueryList #github fulma/Fulma:1e763d852112307370675a0cf692415a53d1993f# github elmish/elmish:v3.x -github fable-compiler/fable-promise:master +github fable-compiler/fable-promise:14eca483d664dce5aebbe50fb92a536a7c1ffe53 github alfonsogarciacaro/Feliz.Engine:main github alfonsogarciacaro/Feliz.Snabbdom:main -github davedawkins/Feliz.Engine.Bulma:main -github davedawkins/Sutil:main +github davedawkins/Feliz.Engine.Bulma:0df54855ac3464a432cd207412ab66e650a87d77 +github davedawkins/Sutil:343bc31867127638a79afede0dcaa97ca56fa264 diff --git a/paket.lock b/paket.lock index b363b3f9..416ec720 100644 --- a/paket.lock +++ b/paket.lock @@ -11,24 +11,39 @@ NUGET Fable.Browser.Svg (>= 2.3) Fable.Core (>= 3.0) FSharp.Core (>= 4.7.2) - Fable.Browser.Dom (2.14) - Fable.Browser.Blob (>= 1.3) - Fable.Browser.Event (>= 1.5) - Fable.Browser.WebStorage (>= 1.2) - Fable.Core (>= 3.2.8) + Fable.Browser.Dom (2.4.4) + Fable.Browser.Blob (>= 1.1.4) + Fable.Browser.Event (>= 1.4.4) + Fable.Browser.WebStorage (>= 1.0.4) + Fable.Core (>= 3.0) FSharp.Core (>= 4.7.2) - Fable.Browser.Event (1.5) - Fable.Browser.Gamepad (>= 1.1) + Fable.Browser.Event (1.4.5) + Fable.Browser.Gamepad (>= 1.0.3) Fable.Core (>= 3.0) FSharp.Core (>= 4.7.2) Fable.Browser.Gamepad (1.2) Fable.Core (>= 3.0) FSharp.Core (>= 4.7.2) + Fable.Browser.Geolocation (1.2) + Fable.Core (>= 3.0) + FSharp.Core (>= 4.7.2) Fable.Browser.MediaQueryList (1.4) Fable.Browser.Dom (>= 2.11) Fable.Browser.Event (>= 1.5) Fable.Core (>= 3.0) FSharp.Core (>= 4.7.2) + Fable.Browser.MediaStream (3.3) + Fable.Browser.Dom (>= 2.11) + Fable.Browser.Event (>= 1.5) + Fable.Core (>= 3.0) + FSharp.Core (>= 4.7.2) + Fable.Browser.Navigator (2.0) + Fable.Browser.Gamepad (>= 1.0.3) + Fable.Browser.Geolocation (>= 1.0.4) + Fable.Browser.MediaStream (>= 3.0.4) + Fable.Browser.Worker (>= 1.0.5) + Fable.Core (>= 3.1.5) + FSharp.Core (>= 4.7.2) Fable.Browser.Svg (2.3) Fable.Browser.Dom (>= 2.11) Fable.Core (>= 3.0) @@ -37,6 +52,10 @@ NUGET Fable.Browser.Event (>= 1.5) Fable.Core (>= 3.0) FSharp.Core (>= 4.7.2) + Fable.Browser.Worker (1.2) + Fable.Browser.Event (>= 1.5) + Fable.Core (>= 3.0) + FSharp.Core (>= 4.7.2) Fable.Core (4.0) Fable.Elmish (4.0.2) Fable.Core (>= 3.7.1) @@ -135,12 +154,12 @@ GITHUB SourceLink.Create.CommandLine Unquote remote: fable-compiler/fable-promise - FULLPROJECT (28ca5a0b62c9fb94fa9ea861eb8abf6cef59e800) + FULLPROJECT (14eca483d664dce5aebbe50fb92a536a7c1ffe53) remote: alfonsogarciacaro/Feliz.Engine FULLPROJECT (61152aebd6c0b7878bfc4db05e2e10724b434a3e) remote: alfonsogarciacaro/Feliz.Snabbdom FULLPROJECT (7b28ccbceebcc9b66a8cb67aab89b7ae9576dbfb) remote: davedawkins/Feliz.Engine.Bulma - FULLPROJECT (9504b9d5a43f5c3ef84086815076ebed1948b162) + FULLPROJECT (0df54855ac3464a432cd207412ab66e650a87d77) remote: davedawkins/Sutil - FULLPROJECT (cba867e22cb7862d64bf363acf81357c1ffc384d) \ No newline at end of file + FULLPROJECT (343bc31867127638a79afede0dcaa97ca56fa264) \ No newline at end of file diff --git a/src/App/App.fsproj b/src/App/App.fsproj index c6435498..75abc262 100644 --- a/src/App/App.fsproj +++ b/src/App/App.fsproj @@ -8,6 +8,7 @@ + diff --git a/src/App/ConsolePanel.fs b/src/App/ConsolePanel.fs index 6b160f46..a27961f6 100644 --- a/src/App/ConsolePanel.fs +++ b/src/App/ConsolePanel.fs @@ -52,7 +52,7 @@ let renderShowSeparator = prop.style [ style.justifyContent.center ] - prop.text "Iframe loaded" + prop.text Translations.msg_iframe_loaded ] let renderBody (isExpanded : bool) (logs : Log list) (setConsoleEnd : HTMLElement -> unit) onContainerScroll = @@ -134,7 +134,7 @@ let consolePanel = Html.div [ prop.className "scrollable-panel-header-title" - prop.text "Console" + prop.text Translations.win_header_console ] Html.div [ diff --git a/src/App/Helpers.fs b/src/App/Helpers.fs index df15c663..540a51e8 100644 --- a/src/App/Helpers.fs +++ b/src/App/Helpers.fs @@ -3,66 +3,117 @@ module Helpers open Feliz -type Html with - static member inline ofOption (element : ReactElement option) : ReactElement = +type Html with + + static member inline ofOption(element: ReactElement option) : ReactElement = match element with - | Some element -> - element - - | None -> - Html.none + | Some element -> element + + | None -> Html.none module Tooltip = open System.Text.RegularExpressions let private stringReplacePatterns = - [ "<", "<" - ">", ">" - """, "\"" - "'", "'" - "&", "&" - "", "**Description**\n\n" - "", "\n" - "", "\n" - "", "\n" - "", "" - "", "\n" ] + [ + "<", "<" + ">", ">" + """, "\"" + "'", "'" + "&", "&" + "", "**Description**\n\n" + "", "\n" + "", "\n" + "", "\n" + "", "" + "", "\n" + ] let private regexReplacePatterns = let r pat = Regex(pat, RegexOptions.IgnoreCase) - let code (strings : string array) = + + let code (strings: string array) = let str = strings.[0] + if str.Contains("\n") then - "```forceNoHighlight" + str + "```" + "```forceNoHighlight" + + str + + "```" else - "`" + str + "`" - let returns = Array.item 0 >> sprintf "\n**Returns**\n\n%s" - let param (s: string[]) = sprintf "* `%s`: %s"(s.[0].Substring(1, s.[0].Length - 2)) s.[1] - [ r"((?:(?!)(?!<\/c>)[\s\S])*)<\/c>", code - r"""((?:(?!<\/see>)[\s\S])*)<\/see>""", code - r"""((?:(?!<\/param>)[\s\S])*)<\/param>""", param - r"""((?:(?!<\/typeparam>)[\s\S])*)<\/typeparam>""", param - r"""((?:(?!<\/exception>)[\s\S])*)<\/exception>""", param - r"""((?:(?!<\/a>)[\s\S])*)<\/a>""", fun s -> (s.[0].Substring(1, s.[0].Length - 2)) - - r"((?:(?!<\/returns>)[\s\S])*)<\/returns>", returns + "`" + + str + + "`" + + let returns = + Array.item 0 + >> sprintf "\n**Returns**\n\n%s" + + let param (s: string[]) = + sprintf + "* `%s`: %s" + (s.[0] + .Substring( + 1, + s.[0].Length + - 2 + )) + s.[1] + + [ + r "((?:(?!)(?!<\/c>)[\s\S])*)<\/c>", code + r + """((?:(?!<\/see>)[\s\S])*)<\/see>""", + code + r + """((?:(?!<\/param>)[\s\S])*)<\/param>""", + param + r + """((?:(?!<\/typeparam>)[\s\S])*)<\/typeparam>""", + param + r + """((?:(?!<\/exception>)[\s\S])*)<\/exception>""", + param + r """((?:(?!<\/a>)[\s\S])*)<\/a>""", + fun s -> + (s.[0] + .Substring( + 1, + s.[0].Length + - 2 + )) + + r "((?:(?!<\/returns>)[\s\S])*)<\/returns>", returns ] /// Helpers to create a new section in the markdown comment - let private suffixXmlKey (tag : string) (value : string) (str : string) = + let private suffixXmlKey (tag: string) (value: string) (str: string) = match str.IndexOf(tag) with - | x when x <> -1 -> + | x when + x + <> -1 + -> let insertAt = - if str.Chars(x - 1) = ' ' then - x - 1 + if + str.Chars( + x + - 1 + ) = ' ' + then + x + - 1 else x + str.Insert(insertAt, value) | _ -> str - let private suffixTypeparam = suffixXmlKey " List.fold (fun res (regex: Regex, formatter: string[] -> string) -> - // repeat replacing with same pattern to handle nested tags, like `......` - let rec loop res : string = - match regex.Match res with - | m when m.Success -> - m.Groups - |> Seq.cast - |> Seq.map (fun g -> g.Value) - |> Seq.toArray - |> Array.splitAt 1 - |> function - | [| firstGroup |], otherGroups -> - loop <| res.Replace(firstGroup, formatter otherGroups) + |> List.fold + (fun res (regex: Regex, formatter: string[] -> string) -> + // repeat replacing with same pattern to handle nested tags, like `......` + let rec loop res : string = + match regex.Match res with + | m when m.Success -> + m.Groups + |> Seq.cast + |> Seq.map (fun g -> g.Value) + |> Seq.toArray + |> Array.splitAt 1 + |> function + | [| firstGroup |], otherGroups -> + loop + <| res.Replace( + firstGroup, + formatter otherGroups + ) + | _ -> res | _ -> res - | _ -> res - loop res - ) str + + loop res + ) + str stringReplacePatterns - |> List.fold (fun (res: string) (oldValue, newValue) -> - res.Replace(oldValue, newValue) - ) res + |> List.fold + (fun (res: string) (oldValue, newValue) -> + res.Replace(oldValue, newValue) + ) + res diff --git a/src/App/Loader.fs b/src/App/Loader.fs index 4d2b099e..c44cb466 100644 --- a/src/App/Loader.fs +++ b/src/App/Loader.fs @@ -66,7 +66,7 @@ let init (result: Option) = let private view (model: Model) dispatch = match model with | Initializing -> - Html.text "Initializing" + Html.text Translations.msg_initializing | Running model -> Main.view model (MainMsg >> dispatch) @@ -91,19 +91,19 @@ let private view (model: Model) dispatch = Bulma.title.h3 [ text.hasTextCentered - prop.text "Fable REPL" + prop.text Translations.msg_repl_name ] Bulma.subtitle.h5 [ text.hasTextCentered - prop.text "For best experience we recommend running the REPL on a desktop" + prop.text Translations.msg_desktop_experience ] Bulma.level [ Bulma.levelItem [ Bulma.button.a [ prop.onClick (fun _ -> Initialize p |> dispatch) - prop.text "Continue" + prop.text Translations.btn_continue ] ] ] diff --git a/src/App/Main.fs b/src/App/Main.fs index 7a33c512..e0f503ef 100644 --- a/src/App/Main.fs +++ b/src/App/Main.fs @@ -171,7 +171,7 @@ let private postToGist = let data = Encode.object [ "public", Encode.bool true - "description", Encode.string "Created with Fable REPL" + "description", Encode.string Translations.msg_gist_description "files", Encode.object [ yield "fable-repl.fs", toContent code yield "fable-repl.html", toContent html @@ -218,7 +218,7 @@ let private loadGist = let private showGlobalErrorToast msg = Toast.message msg - |> Toast.title "Failed to compile" + |> Toast.title Translations.msg_compilation_failed |> Toast.position Toast.BottomRight |> Toast.icon Fa.Solid.Exclamation |> Toast.noTimeout @@ -271,7 +271,7 @@ let update msg (model : Model) = ] | LoadFail -> - let msg = "Assemblies couldn't be loaded. Some firewalls prevent download of binary files, please check." + let msg = Translations.msg_assemblies_load_failed { model with State = Idle } @@ -337,14 +337,14 @@ let update msg (model : Model) = JS.console.error exn model - , Toast.message "An error occured when creating the gist. Is the token valid?" + , Toast.message Translations.msg_gist_token_invalid |> Toast.icon Fa.Solid.ExclamationTriangle |> Toast.position Toast.BottomRight |> Toast.warning | NoToken -> model - , Toast.message "You need to register your GitHub API token before sharing to Gist" + , Toast.message Translations.msg_gist_token_missing |> Toast.icon Fa.Solid.ExclamationTriangle |> Toast.position Toast.BottomRight |> Toast.warning @@ -376,7 +376,7 @@ let update msg (model : Model) = | Some code -> code | None -> model.FSharpCode let opts = model.Sidebar.Options - let language = model.Sidebar.Options.Language + let language = model.Sidebar.Options.Target CompileCode(code, language, opts.ToOtherFSharpOptions) |> model.Worker.Post { model with @@ -390,7 +390,7 @@ let update msg (model : Model) = let hasCriticalErrors = errors |> Array.exists (fun e -> not e.IsWarning) if hasCriticalErrors then let toastCmd = - Toast.message "Failed to compile" + Toast.message Translations.msg_compilation_failed |> Toast.position Toast.BottomRight |> Toast.icon Fa.Solid.Exclamation |> Toast.dismissOnClick @@ -410,7 +410,7 @@ let update msg (model : Model) = | _ -> [fun _ -> saveModel model] let cmd2 = - Toast.message "Compiled successfuly" + Toast.message Translations.msg_compilation_successful |> Toast.position Toast.BottomRight |> Toast.icon Fa.Solid.Check |> Toast.dismissOnClick @@ -565,8 +565,8 @@ let update msg (model : Model) = | ShareableUrlReady () -> model - , Toast.message "Copy it from the address bar" - |> Toast.title "Shareable link is ready" + , Toast.message Translations.msg_shareable_url_ready_text + |> Toast.title Translations.msg_shareable_url_ready_title |> Toast.position Toast.BottomRight |> Toast.icon Fa.Solid.InfoCircle |> Toast.timeout (System.TimeSpan.FromSeconds 5.) @@ -575,7 +575,7 @@ let update msg (model : Model) = | LoadGistError exn -> JS.console.error exn model - , Toast.message "An error occured when loading the gist" + , Toast.message Translations.msg_load_gist_error |> Toast.icon Fa.Solid.ExclamationTriangle |> Toast.position Toast.BottomRight |> Toast.warning @@ -583,7 +583,7 @@ let update msg (model : Model) = | UpdateQueryFailed exn -> JS.console.error exn model - , Toast.message "An error occured when updating the URL" + , Toast.message Translations.msg_update_url_failed |> Toast.icon Fa.Solid.ExclamationTriangle |> Toast.position Toast.BottomRight |> Toast.warning @@ -757,12 +757,12 @@ let private problemsPanel (isExpanded : bool) (errors : Monaco.Editor.IMarkerDat let title = if errors.Length = 0 then Html.span [ - prop.text "Problems" + prop.text Translations.msg_problems ] else Html.span [ prop.children [ - Html.text "Problems: " + Html.text Translations.msg_problems_info Html.span [ prop.style [ style.marginLeft (length.em 0.5) @@ -815,7 +815,7 @@ let private problemsPanel (isExpanded : bool) (errors : Monaco.Editor.IMarkerDat match error.severity with | Monaco.MarkerSeverity.Error -> Fa.Solid.TimesCircle, color.isDanger, "is-danger" | Monaco.MarkerSeverity.Warning -> Fa.Solid.ExclamationTriangle, color.isWarning, "is-warning" - | _ -> failwith "Should not happen", color.isDanger, "" + | _ -> failwith Translations.msg_fatal_error, color.isDanger, "" Html.div [ prop.className ("scrollable-panel-body-row " + colorClass) @@ -984,7 +984,7 @@ let private outputTabs (activeTab : OutputTab) dispatch = ) prop.children [ Html.a [ - prop.text "Live sample" + prop.text Translations.msg_live_sample_text ] ] ] @@ -997,7 +997,7 @@ let private outputTabs (activeTab : OutputTab) dispatch = ) prop.children [ Html.a [ - prop.text "Code" + prop.text Translations.msg_code_text ] ] ] @@ -1034,7 +1034,7 @@ let private viewCodeEditor (model: Model) dispatch = editor.isHidden (model.OutputTab <> OutputTab.Code) editor.customClass (fontSizeClass model.Sidebar.Options.FontSize) editor.editorDidMount (onJsEditorDidMount model dispatch) - editor.language (model.Sidebar.Options.Language.ToLower()) + editor.language (model.Sidebar.Options.Target.ToLower()) ] let private outputArea model dispatch = diff --git a/src/App/Mouse.fs b/src/App/Mouse.fs index 0ac959d7..cd0f08a1 100644 --- a/src/App/Mouse.fs +++ b/src/App/Mouse.fs @@ -63,7 +63,10 @@ module Cmd = |> Decode.map args.ConsoleErrorCor | x -> // Discard messages we don't know how to handle it - sprintf "`%A` is not a known value for an iframe message" x + let formatMessage = + PrintfFormat string,unit,string,string>(Translations.msg_invalid_iframe_error) + + sprintf formatMessage x |> Decode.fail ) Decode.fromValue "$" iframeMessageDecoder ev?data diff --git a/src/App/Sidebar.fs b/src/App/Sidebar.fs index 6a634683..645c25aa 100644 --- a/src/App/Sidebar.fs +++ b/src/App/Sidebar.fs @@ -222,7 +222,7 @@ let private collapseButton dispatch = prop.onClick (fun _ -> dispatch ToggleState) prop.children [ Bulma.cardHeader [ - Bulma.cardHeaderTitle.div "Collapse sidebar" + Bulma.cardHeaderTitle.div Translations.msg_collapse_sidebar Bulma.cardHeaderIcon.span [ Bulma.icon [ Fa.i [ Fa.Solid.AngleDoubleLeft; Fa.Size Fa.FaLarge ] [ ] @@ -242,7 +242,7 @@ let private sidebarContainer dispatch (sections : ReactElement list) = Html.img [ prop.src "img/fable-ionide.png" ] - Bulma.title.h4 "Fable REPL" + Bulma.title.h4 Translations.msg_repl_name ] ] @@ -274,11 +274,11 @@ let view (isCompiling : bool) (model: Model) dispatch = let widgets = [ if model.IsExpanded then - "General", Fa.Solid.Th, Widgets.General.viewExpanded isCompiling model.Options.GistToken model.General (GeneralMsg >> dispatch), None - "Samples", Fa.Solid.Book, Widgets.Samples.view model.Samples (SamplesMsg >> dispatch), Some 500 - "Options", Fa.Solid.Cog, Widgets.Options.view model.Options (OptionsMsg >> dispatch), None - "Statistics", Fa.Regular.Clock, Widgets.Stats.view model.Statistics, None - "About", Fa.Solid.Info, Widgets.About.view model.FableVersion, None + Translations.msg_widget_general, Fa.Solid.Th, Widgets.General.viewExpanded isCompiling model.Options.GistToken model.General (GeneralMsg >> dispatch), None + Translations.msg_widget_samples, Fa.Solid.Book, Widgets.Samples.view model.Samples (SamplesMsg >> dispatch), Some 500 + Translations.msg_widget_options, Fa.Solid.Cog, Widgets.Options.view model.Options (OptionsMsg >> dispatch), None + Translations.msg_widget_statistics, Fa.Regular.Clock, Widgets.Stats.view model.Statistics, None + Translations.msg_widget_about, Fa.Solid.Info, Widgets.About.view model.FableVersion, None ] |> List.map (renderWidgets model dispatch) diff --git a/src/App/Translations.fs b/src/App/Translations.fs new file mode 100644 index 00000000..85efebe9 --- /dev/null +++ b/src/App/Translations.fs @@ -0,0 +1,289 @@ +[] +module Translations + +open Browser +open Fable.Core + +let enTranslations = + {| + english = "English" + russian = "Russian" + ukrainian = "Ukrainian" + msg_iframe_loaded = "Iframe loaded" + win_header_console = "Console" + msg_initializing = "Initializing" + msg_repl_name = "Fable REPL" + msg_desktop_experience = + "For best experience we recommend running the REPL on a desktop" + btn_continue = "Continue" + msg_gist_description = "Created with Fable REPL" + msg_compilation_failed = "Failed to compile" + msg_assemblies_load_failed = + "Assemblies couldn't be loaded. Some firewalls prevent download of binary files, please check." + msg_gist_token_invalid = + "An error occurred when creating the gist. Is the token valid?" + msg_gist_token_missing = + "You need to register your GitHub API token before sharing to Gist" + msg_compilation_successful = "Compiled successfully" + msg_shareable_url_ready_text = "Copy it from the address bar" + msg_shareable_url_ready_title = "Shareable link is ready" + msg_load_gist_error = "An error occurred when loading the gist" + msg_update_url_failed = "An error occurred when updating the URL" + msg_fatal_error = "Should not happen" + msg_live_sample_text = "Live sample" + msg_code_text = "Code" + msg_problems = "Problems" + msg_problems_info = "Problems = " + msg_invalid_iframe_error = + "`%A` is not a known value for an iframe message" + msg_collapse_sidebar = "Collapse sidebar" + msg_widget_general = "General" + msg_widget_samples = "Samples" + msg_widget_options = "Options" + msg_widget_statistics = "Statistics" + msg_widget_about = "About" + msg_found_a_bug = "Found a bug ?" + msg_general_compile_run_tooltip = "Compile and run (Alt+Enter)" + msg_general_compile_run_text = "Compile and run" + msg_general_refresh_sample_tooltip = + "Refresh the live sample (without compiling)" + msg_general_refresh_sample_text = "Refresh the live sample" + msg_general_reset_repl_tooltip = + "Reset the REPL, you will lose your current work" + msg_general_reset_repl_text = "Click here to reset" + msg_general_share_url_tooltip = "Share using the URL" + msg_general_share_url_text = "Share using the URL" + msg_general_share_gist_tooltip = "Share to Gist" + msg_general_share_gist_text = "Share to Gist" + msg_reset_confirmation_text = "Please, confirm to reset" + btn_confirm = "Confirm" + btn_cancel = "Cancel" + btn_save = "Save" + msg_options_editor_font_size = "Editors font size" + msg_options_editor_font_family = "Editors font family" + msg_options_size_small = "Small" + msg_options_size_medium = "Medium" + msg_options_size_large = "Large" + msg_options_programming_language = "Target" + msg_options_interface_language = "Language" + msg_options_settings_optimize = "Optimize (experimental)" + msg_options_settings_debug = "Define DEBUG" + msg_options_settings_typed_arrays = "Typed Arrays" + msg_options_gist_token_delete = "Delete gist token" + msg_options_gist_token_github_token = "Github token" + msg_options_gist_token_github_token_create = " (Create)" + msg_options_gist_token_gist_scope = "Token with gist scope" + msg_samples_refresh_samples = "Refresh samples" + msg_stats_steps = "Steps" + msg_stats_milliseconds_short = "ms" + |} + +let ukTranslations = + {| + english = "Англійська" + russian = "Російський" + ukrainian = "Українська" + msg_iframe_loaded = "Iframe завантажено" + win_header_console = "Консоль" + msg_initializing = "Ініціалізуємо" + msg_repl_name = "Fable REPL" + msg_desktop_experience = + "Для найкращого досвіду ми рекомендуємо запускати REPL із десктопа" + btn_continue = "Продовжити" + msg_gist_description = "Створено із REPL" + msg_compilation_failed = "Не вдалося скомпілювати" + msg_assemblies_load_failed = + "Не вдалося завантажити збірки. Деякі брандмауери запобігають завантаженню бінарних файлів, перевірте." + msg_gist_token_invalid = + "Під час створення gist-у сталася помилка. Чи токен дійсний?" + msg_gist_token_missing = + "Вам потрібно зареєструвати свій токен API GitHub перед тим, як надати спільний доступ до Gist" + msg_compilation_successful = "Скомпільовано успішно" + msg_shareable_url_ready_text = "Скопіюйте його з адресного рядка" + msg_shareable_url_ready_title = + "Посилання для спільного доступу готове" + msg_load_gist_error = "Під час завантаження gist-у сталася помилка" + msg_update_url_failed = + "Під час оновлення URL-адреси сталася помилка" + msg_fatal_error = "Не повинно траплятися" + msg_live_sample_text = "Інтерактивний приклад" + msg_code_text = "Код" + msg_problems = "Проблеми" + msg_problems_info = "Проблеми = " + msg_invalid_iframe_error = + "Невідоме значення `%A` для повідомлення від iframe" + msg_collapse_sidebar = "Згорнути бічну панель" + msg_widget_general = "Загальний" + msg_widget_samples = "Приклади" + msg_widget_options = "Опції" + msg_widget_statistics = "Статистика" + msg_widget_about = "О REPL" + msg_found_a_bug = "Знайшов помилку ?" + msg_general_compile_run_tooltip = + "Скомпілювати та запустити (Alt+Enter)" + msg_general_compile_run_text = "Скомпілювати та запустити" + msg_general_refresh_sample_tooltip = + "Оновити інтерактивний приклад (без компіляції)" + msg_general_refresh_sample_text = "Оновити інтерактивний приклад" + msg_general_reset_repl_tooltip = + "Скинувши REPL, ви втратите поточну роботу" + msg_general_reset_repl_text = + "Скинувши REPL, ви втратите поточну роботу" + msg_general_share_url_tooltip = "Поділіться за допомогою URL-адреси" + msg_general_share_url_text = "Поділіться за допомогою URL-адреси" + msg_general_share_gist_tooltip = "Поділитися через Gist" + msg_general_share_gist_text = "Поділитися через Gist" + msg_reset_confirmation_text = "Підтвердьте, щоб скинути" + btn_confirm = "Підтвердити" + btn_cancel = "Скасувати" + btn_save = "Зберегти" + msg_options_editor_font_size = "Розмір шрифта редактора" + msg_options_editor_font_family = "Сімейство шрифта редактора" + msg_options_size_small = "Малий" + msg_options_size_medium = "Середній" + msg_options_size_large = "Великий" + msg_options_interface_language = "Мова" + msg_options_programming_language = "Цільова" + msg_options_settings_optimize = "Оптимизувати (експеріментально)" + msg_options_settings_debug = "Визначити DEBUG" + msg_options_settings_typed_arrays = "Типізовані масиви" + msg_options_gist_token_delete = "Видалити токен gist" + msg_options_gist_token_github_token = "Github токен" + msg_options_gist_token_github_token_create = " (Створити)" + msg_options_gist_token_gist_scope = "Токен із gist скоупом" + msg_samples_refresh_samples = "Оновити приклади" + msg_stats_steps = "Кроки" + msg_stats_milliseconds_short = "мс" + |} + +let ruTranslations = + {| + english = "Английский" + russian = "Русский" + ukrainian = "Украинец" + msg_iframe_loaded = "Iframe загружен" + win_header_console = "Консоль" + msg_initializing = "Инициализируем" + msg_repl_name = "Fable REPL" + msg_desktop_experience = + "Для наилучшего опыта мы рекомендуем запускать REPL с десктопа" + btn_continue = "Продолжить" + msg_gist_description = "Создано из REPL" + msg_compilation_failed = "Не удалось скомпилировать" + msg_assemblies_load_failed = + "Не удалось скачать зборки. Некоторые брэндмауэры предотвращают загрузку бинарных файлов, проверьте." + msg_gist_token_invalid = + "Во время создания gist-а произошла ошибка. Токен действителен?" + msg_gist_token_missing = + "Вам необходимо зарегистрировать свой токен API GitHub перед тим, как дать совместный доступ к Gist" + msg_compilation_successful = "Скомпилировано успешно" + msg_shareable_url_ready_text = "Скопируйте ее и адресной строки" + msg_shareable_url_ready_title = + "Ссылка для совместного доступа готова" + msg_load_gist_error = "Во время загрузки gist-а произошла ошибка" + msg_update_url_failed = + "Во время обновления URL-адреса произошла ошибка" + msg_fatal_error = "Не должно случаться" + msg_live_sample_text = "Интерактивний пример" + msg_code_text = "Код" + msg_problems = "Проблемы" + msg_problems_info = "Проблемы = " + msg_invalid_iframe_error = + "Неизвестное значення `%A` для сообщения от iframe" + msg_collapse_sidebar = "Свернуть боковую панель" + msg_widget_general = "Общее" + msg_widget_samples = "Примеры" + msg_widget_options = "Опции" + msg_widget_statistics = "Статистика" + msg_widget_about = "О REPL" + msg_found_a_bug = "Нашел ошибку?" + msg_general_compile_run_tooltip = + "Скомпилировать и запустить (Alt+Enter)" + msg_general_compile_run_text = "Скомпилировать и запустить" + msg_general_refresh_sample_tooltip = + "Оновить интерактивний пример (без компиляции)" + msg_general_refresh_sample_text = "Оновить интерактивний пример" + msg_general_reset_repl_tooltip = + "Сбрасывая REPL, ви потеряете текущую работу" + msg_general_reset_repl_text = + "Сбрасывая REPL, ви потеряете текущую работу" + msg_general_share_url_tooltip = "Поделится с помощью URL-адреса" + msg_general_share_url_text = "Поделится с помощью URL-адреса" + msg_general_share_gist_tooltip = "Поделится через Gist" + msg_general_share_gist_text = "Поделится через Gist" + msg_reset_confirmation_text = "Подтвердите, чтобы сбросить" + btn_confirm = "Подтвердить" + btn_cancel = "Отменить" + btn_save = "Сохранить" + msg_options_editor_font_size = "Размер шрифта редактора" + msg_options_editor_font_family = "Семейство шрифта редактора" + msg_options_size_small = "Маленький" + msg_options_size_medium = "Средний" + msg_options_size_large = "Большой" + msg_options_programming_language = "Язык" + msg_options_interface_language = "Язык" + msg_options_settings_optimize = "Оптимизировать (экспериментально)" + msg_options_settings_debug = "Определить DEBUG" + msg_options_settings_typed_arrays = "Типизированные массивы" + msg_options_gist_token_delete = "Удалить токен gist" + msg_options_gist_token_github_token = "Github токен" + msg_options_gist_token_github_token_create = " (Создать)" + msg_options_gist_token_gist_scope = "Токен с gist скоупом" + msg_samples_refresh_samples = "Обновить примеры" + msg_stats_steps = "Шаги" + msg_stats_milliseconds_short = "мс" + |} + +[] +[] +type LanguageCode = + | [] English + | [] Ukrainian + | [] Russian + +module LanguageCode = + + let isValidText (code : string) = + match code with + | "en" | "uk" | "ru" -> true + | _ -> false + + let fromText (code : string) = + match code with + | "en" -> LanguageCode.English + | "uk" -> LanguageCode.Ukrainian + | "ru" -> LanguageCode.Russian + | _ -> LanguageCode.English + +let translations = + dict + [ + LanguageCode.English, enTranslations + LanguageCode.Ukrainian, ukTranslations + LanguageCode.Russian, ruTranslations + ] + +[] +let LOCAL_STORAGE_KEY = "fable_repl_language" + +let activeLanguageCode = + let storedLanguageCode = localStorage.[LOCAL_STORAGE_KEY] + // Try to get the language from the local storage in case the user + // changed it in the settings + if storedLanguageCode <> null && LanguageCode.isValidText storedLanguageCode then + LanguageCode.fromText storedLanguageCode + else + navigator.language + |> Option.map (fun lang -> + let languageCode = lang.Substring(0, 2) + + LanguageCode.fromText languageCode + ) + |> Option.defaultValue LanguageCode.English + +let storedLanguageCode (code : LanguageCode) = + // Code is a string enum, so it is already a string at runtime + localStorage.[LOCAL_STORAGE_KEY] <- unbox code + +let Translations = + translations[activeLanguageCode] diff --git a/src/App/Widgets/About.fs b/src/App/Widgets/About.fs index 50e52ef7..f667425f 100644 --- a/src/App/Widgets/About.fs +++ b/src/App/Widgets/About.fs @@ -17,7 +17,7 @@ let view fableVersion = prop.style [ style.textDecoration.underline ] - prop.text "Found a bug ?" + prop.text Translations.msg_found_a_bug ] ] ] diff --git a/src/App/Widgets/General.fs b/src/App/Widgets/General.fs index f3edae47..1f082d9c 100644 --- a/src/App/Widgets/General.fs +++ b/src/App/Widgets/General.fs @@ -99,13 +99,13 @@ let viewCollapsed (isCompiling : bool) (gistToken : string option) (model: Model prop.className "actions-area" prop.children [ - actionButton "Compile and run (Alt+Enter)" StartCompile compileIcon - actionButton "Refresh the live sample (without compiling)" RefreshIframe [ Fa.i [ Fa.Solid.Redo ] [ ] ] - actionButton "Reset the REPL, you will lose your current work" AskReset [ Fa.i [ Fa.Solid.TrashAlt ] [ ] ] - actionButton "Share using the URL" Share [ Fa.i [ Fa.Solid.Share ] [ ] ] + actionButton Translations.msg_general_compile_run_tooltip StartCompile compileIcon + actionButton Translations.msg_general_refresh_sample_tooltip RefreshIframe [ Fa.i [ Fa.Solid.Redo ] [ ] ] + actionButton Translations.msg_general_reset_repl_tooltip AskReset [ Fa.i [ Fa.Solid.TrashAlt ] [ ] ] + actionButton Translations.msg_general_share_url_tooltip Share [ Fa.i [ Fa.Solid.Share ] [ ] ] match gistToken with | Some _ -> - actionButton "Share to Gist" ShareToGist [ Fa.i [ Fa.Brand.Github ] [ ] ] + actionButton Translations.msg_general_share_gist_tooltip ShareToGist [ Fa.i [ Fa.Brand.Github ] [ ] ] | None -> Html.none ] @@ -150,13 +150,13 @@ let viewExpanded (isCompiling : bool) (gistToken : string option) (model: Model) match model.ResetState with | Default -> Html.div [ - renderItem "Compile and run" isCompiling StartCompile Fa.Solid.Play - renderItem "Refresh the live sample" isCompiling RefreshIframe Fa.Solid.Redo - renderItem "Click here to reset" false AskReset Fa.Solid.TrashAlt - renderItem "Share using the URL" false Share Fa.Solid.Share + renderItem Translations.msg_general_compile_run_text isCompiling StartCompile Fa.Solid.Play + renderItem Translations.msg_general_refresh_sample_text isCompiling RefreshIframe Fa.Solid.Redo + renderItem Translations.msg_general_reset_repl_text false AskReset Fa.Solid.TrashAlt + renderItem Translations.msg_general_share_url_text false Share Fa.Solid.Share match gistToken with | Some _ -> - renderItem "Share to Gist" false ShareToGist Fa.Brand.Github + renderItem Translations.msg_general_share_gist_text false ShareToGist Fa.Brand.Github | None -> Html.none ] @@ -165,7 +165,7 @@ let viewExpanded (isCompiling : bool) (gistToken : string option) (model: Model) Bulma.field.div [ Bulma.help [ color.isWarning - prop.text "Please, confirm to reset" + prop.text Translations.msg_reset_confirmation_text ] Bulma.field.div [ @@ -179,7 +179,7 @@ let viewExpanded (isCompiling : bool) (gistToken : string option) (model: Model) Bulma.icon [ Fa.i [ Fa.Solid.Check ] [ ] ] - Html.span "Confirm" + Html.span Translations.btn_confirm ] ] ] @@ -192,7 +192,7 @@ let viewExpanded (isCompiling : bool) (gistToken : string option) (model: Model) Bulma.icon [ Fa.i [ Fa.Solid.Times ] [ ] ] - Html.span "Cancel" + Html.span Translations.btn_cancel ] ] ] @@ -223,7 +223,7 @@ let viewModalResetConfirmation (model: Model) dispatch = prop.children [ Html.span [ prop.className "reset-confirmation-modal-content-text" - prop.text "Please, confirm to reset" + prop.text Translations.msg_reset_confirmation_text ] Html.div [ @@ -243,7 +243,7 @@ let viewModalResetConfirmation (model: Model) dispatch = Fa.i [ Fa.Solid.Check ] [ ] ] - Html.span "Confirm" + Html.span Translations.btn_confirm ] ] ] @@ -257,7 +257,7 @@ let viewModalResetConfirmation (model: Model) dispatch = Fa.i [ Fa.Solid.Times ] [ ] ] - Html.span "Cancel" + Html.span Translations.btn_cancel ] ] ] diff --git a/src/App/Widgets/Options.fs b/src/App/Widgets/Options.fs index 4587581d..c9810928 100644 --- a/src/App/Widgets/Options.fs +++ b/src/App/Widgets/Options.fs @@ -18,7 +18,7 @@ type Model = { Optimize : bool DefineDebug : bool TypedArrays : bool - Language : string + Target : string FontSize : float FontFamily : string GistToken : string option @@ -33,7 +33,7 @@ type Model = { Optimize = false DefineDebug = true TypedArrays = true - Language = "javascript" + Target = "javascript" FontSize = 14. FontFamily = MONACO_DEFAULT_FONT_FAMILY GistToken = None @@ -47,7 +47,7 @@ type Model = // |> Option.defaultValue false DefineDebug = get.Optional.Field "defineDebug" Decode.bool |> Option.defaultValue true TypedArrays = get.Optional.Field "typedArrays" Decode.bool |> Option.defaultValue true - Language = get.Optional.Field "language" Decode.string |> Option.defaultValue "javascript" + Target = get.Optional.Field "language" Decode.string |> Option.defaultValue "javascript" FontSize = get.Optional.Field "fontSize" Decode.float |> Option.defaultValue 14. FontFamily = get.Optional.Field "fontFamily" Decode.string @@ -61,7 +61,7 @@ type Model = [ yield "optimize", Encode.bool model.Optimize yield "defineDebug", Encode.bool model.DefineDebug yield "typedArrays", Encode.bool model.TypedArrays - yield "language", Encode.string model.Language + yield "language", Encode.string model.Target yield "fontSize", Encode.float model.FontSize yield "fontFamily", Encode.string model.FontFamily match model.GistToken with @@ -117,7 +117,7 @@ let update msg model = { model with TypedArrays = not model.TypedArrays }, ExtMsg.NoOp | ChangeLanguage newLang -> - { model with Language = newLang }, ExtMsg.Recompile + { model with Target = newLang }, ExtMsg.Recompile | ChangeFontSize newSize -> { model with FontSize = newSize }, ExtMsg.NoOp @@ -136,7 +136,7 @@ let update msg model = { model with GistToken = None}, ExtMsg.NoOp // Save the setting in localStorage - // Like that they are persistant between REPL reload + // Like that they are persistent between REPL reload |> saveSettings let private fontSizeOption (label : string) (fontSize : float) = @@ -147,7 +147,7 @@ let private fontSizeOption (label : string) (fontSize : float) = let inline private fontSizeSetting (fontSize : float) dispatch = Bulma.field.div [ - Bulma.label "Editors font size" + Bulma.label Translations.msg_options_editor_font_size Bulma.control.div [ Bulma.select [ @@ -157,9 +157,9 @@ let inline private fontSizeSetting (fontSize : float) dispatch = ev.Value |> float |> ChangeFontSize |> dispatch ) prop.children [ - fontSizeOption "Small" 12. - fontSizeOption "Medium" 14. - fontSizeOption "Large" 16. + fontSizeOption Translations.msg_options_size_small 12. + fontSizeOption Translations.msg_options_size_medium 14. + fontSizeOption Translations.msg_options_size_large 16. ] ] ] @@ -173,7 +173,7 @@ let private fontFamilyOption (label : string) (fontFamily : string) = let inline private fontFamilySetting (fontFamily : string) dispatch = Bulma.field.div [ - Bulma.label "Editors font family" + Bulma.label Translations.msg_options_editor_font_family Bulma.control.div [ Bulma.select [ @@ -190,7 +190,7 @@ let inline private fontFamilySetting (fontFamily : string) dispatch = ] ] -let private languageSetting (language : string) dispatch = +let private targetSetting (target : string) dispatch = let option (txt: string) = Html.option [ prop.value txt @@ -198,12 +198,12 @@ let private languageSetting (language : string) dispatch = ] Bulma.field.div [ - Bulma.label "Language" + Bulma.label Translations.msg_options_programming_language Bulma.control.div [ Bulma.select [ select.isFullWidth - prop.value language + prop.value target prop.onChange (fun (ev : Types.Event) -> ev.Value |> ChangeLanguage |> dispatch ) @@ -218,6 +218,34 @@ let private languageSetting (language : string) dispatch = ] ] +let private languageSetting= + let option (code : LanguageCode) (txt: string) = + Html.option [ + prop.value (unbox code) + prop.text txt + ] + + Bulma.field.div [ + Bulma.label Translations.msg_options_interface_language + + Bulma.control.div [ + Bulma.select [ + select.isFullWidth + prop.value (unbox activeLanguageCode) + prop.onChange (fun (ev : Types.Event) -> + LanguageCode.fromText ev.Value |> storedLanguageCode + // Naive way to refresh the page and update the translations + window.location.reload() + ) + prop.children [ + option LanguageCode.English Translations.english + option LanguageCode.Russian Translations.russian + option LanguageCode.Ukrainian Translations.ukrainian + ] + ] + ] + ] + let private switchOption (label : string) isActive dispatch msg = Bulma.field.div [ Bulma.control.div [ @@ -240,13 +268,13 @@ let private switchOption (label : string) isActive dispatch msg = ] let inline private optimizeSetting (model: Model) dispatch = - switchOption "Optimize (experimental)" model.Optimize dispatch ToggleOptimize + switchOption Translations.msg_options_settings_optimize model.Optimize dispatch ToggleOptimize let private defineDebugSetting (model: Model) dispatch = - switchOption "Define DEBUG" model.DefineDebug dispatch ToggleDefineDebug + switchOption Translations.msg_options_settings_debug model.DefineDebug dispatch ToggleDefineDebug let private typedArraysSetting (model: Model) dispatch = - switchOption "Typed Arrays" model.TypedArrays dispatch ToggleTypedArrays + switchOption Translations.msg_options_settings_typed_arrays model.TypedArrays dispatch ToggleTypedArrays let inline private gistTokenSetting (token : string option) (tokenField : string) dispatch = match token with @@ -255,7 +283,7 @@ let inline private gistTokenSetting (token : string option) (tokenField : string Bulma.button.a [ prop.onClick (fun _ -> dispatch DeleteToken) button.isFullWidth - prop.text "Delete gist token" + prop.text Translations.msg_options_gist_token_delete ] ] @@ -263,11 +291,11 @@ let inline private gistTokenSetting (token : string option) (tokenField : string Bulma.field.div [ Bulma.label [ prop.children [ - Html.text "Github token" + Html.text Translations.msg_options_gist_token_github_token Html.a [ prop.target "_blank" prop.href "https://github.com/settings/tokens/new?description=fable-repl&scopes=gist" - prop.text " (Create)" + prop.text Translations.msg_options_gist_token_github_token_create ] ] ] @@ -277,13 +305,13 @@ let inline private gistTokenSetting (token : string option) (tokenField : string prop.children [ Bulma.input.password [ prop.onChange (fun (ev : Types.Event) -> ev.Value |> ChangeGistToken |> dispatch) - prop.placeholder "Token with gist scope" + prop.placeholder Translations.msg_options_gist_token_gist_scope ] if tokenField.Length = 40 then Bulma.button.a [ prop.onClick (fun _ -> dispatch SaveToken) - prop.text "Save" + prop.text Translations.btn_save ] ] ] @@ -297,10 +325,11 @@ let view (model: Model) dispatch = prop.children [ defineDebugSetting model dispatch typedArraysSetting model dispatch - languageSetting model.Language dispatch + targetSetting model.Target dispatch fontFamilySetting model.FontFamily dispatch fontSizeSetting model.FontSize dispatch gistTokenSetting model.GistToken model.GistTokenField dispatch + languageSetting // TODO: Optimize is disabled to prevent problems with inline functions in REPL Lib // optimizeSetting model dispatch ] diff --git a/src/App/Widgets/Samples.fs b/src/App/Widgets/Samples.fs index 6679e729..1e62b10c 100644 --- a/src/App/Widgets/Samples.fs +++ b/src/App/Widgets/Samples.fs @@ -79,8 +79,8 @@ and MenuType = | "menu-item" -> MenuType.DecodeMenuItem - | unkown -> - sprintf "Unkown type `%s` for the sample" unkown + | unknown -> + sprintf "Unknown type `%s` for the sample" unknown |> Decode.fail ) @@ -301,7 +301,7 @@ let view model dispatch = prop.onClick fetchSamplesMsg prop.children [ - Html.span "Refresh samples" + Html.span Translations.msg_samples_refresh_samples ] ] ] @@ -315,4 +315,3 @@ let view model dispatch = #endif Bulma.menu menus - \ No newline at end of file diff --git a/src/App/Widgets/Stats.fs b/src/App/Widgets/Stats.fs index 264a8efd..9395b034 100644 --- a/src/App/Widgets/Stats.fs +++ b/src/App/Widgets/Stats.fs @@ -26,10 +26,10 @@ let view (model : Model) = Bulma.table [ Html.thead [ Html.tr [ - Html.th "Steps" + Html.th Translations.msg_stats_steps Html.th [ prop.className "has-text-right" - prop.text "ms" + prop.text Translations.msg_stats_milliseconds_short ] ] ] diff --git a/src/App/paket.references b/src/App/paket.references index f3bf4f70..02c8b5f2 100644 --- a/src/App/paket.references +++ b/src/App/paket.references @@ -9,4 +9,5 @@ Fable.Fetch Fable.FontAwesome.Free Feliz Feliz.Bulma -Feliz.Bulma.Tooltip \ No newline at end of file +Feliz.Bulma.Tooltip +Fable.Browser.Navigator \ No newline at end of file diff --git a/src/App/public/samples/Samples.fsproj b/src/App/public/samples/Samples.fsproj index 195f49bd..12bf1973 100644 --- a/src/App/public/samples/Samples.fsproj +++ b/src/App/public/samples/Samples.fsproj @@ -72,4 +72,5 @@ - + + \ No newline at end of file diff --git a/src/App/public/samples/elmish/thoth_random_user.fs b/src/App/public/samples/elmish/thoth_random_user.fs index a3888720..1068e4d9 100644 --- a/src/App/public/samples/elmish/thoth_random_user.fs +++ b/src/App/public/samples/elmish/thoth_random_user.fs @@ -109,7 +109,7 @@ let update (msg:Msg) (model:Model) = JS.console.error msg Errored, Cmd.none - // An error occured, when fetching the new user + // An error occurred, when fetching the new user | FetchError error -> JS.console.error error.Message Errored, Cmd.none @@ -134,7 +134,7 @@ let private viewLoading = viewMessage "is-info" "Waiting the server response..." let private viewErrored = - viewMessage "is-danger" "An error occured, please check the console for more information." + viewMessage "is-danger" "An error occurred, please check the console for more information." let private viewUser (user : User) = let birthday = @@ -198,4 +198,4 @@ let view model dispatch = // App Program.mkProgram init update view |> Program.withReactSynchronous "elmish-app" -|> Program.run \ No newline at end of file +|> Program.run diff --git a/src/App/public/samples/elmish/validation.fs b/src/App/public/samples/elmish/validation.fs index c1c71ad5..780125e9 100644 --- a/src/App/public/samples/elmish/validation.fs +++ b/src/App/public/samples/elmish/validation.fs @@ -57,7 +57,7 @@ module Http = Cmd.OfAsync.either loginAsync info successHandler - (fun ex -> LoginFailed "Unknown error occured while logging you in") + (fun ex -> LoginFailed "Unknown error occurred while logging you in") let init() = @@ -215,4 +215,4 @@ let render (state: State) dispatch = Program.mkProgram init update render |> Program.withReactSynchronous "elmish-app" -|> Program.run \ No newline at end of file +|> Program.run