Skip to content

Commit

Permalink
Add infrastructure to be able translate REPL
Browse files Browse the repository at this point in the history
This is based on suggestions from fable-compiler#185

I have translation for Ukrainian and Russian, but want to gather feedback on the approach.
I think that's it, and no additional strings needed, or that amount would be very small.

I have to add dependency on Fable.Browser.Navigator since tha allow account for user preferences.
  • Loading branch information
kant2002 committed Sep 16, 2023
1 parent 081d22a commit 64ddaa1
Show file tree
Hide file tree
Showing 14 changed files with 224 additions and 70 deletions.
11 changes: 6 additions & 5 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -32,11 +33,11 @@ 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

group netcorebuild
source https://www.nuget.org/api/v2
Expand Down
19 changes: 19 additions & 0 deletions paket.lock
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,26 @@ NUGET
Fable.Browser.Gamepad (1.0.3)
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.1.5)
Fable.Browser.Dom (>= 2.4.4)
Fable.Browser.Event (>= 1.4.4)
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.0.4)
Fable.Browser.Dom (>= 2.4.4)
Fable.Core (>= 3.0)
Expand All @@ -37,6 +52,10 @@ NUGET
Fable.Browser.Event (>= 1.4.4)
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 (3.2.8)
FSharp.Core (>= 4.7.2)
Fable.Elmish (3.1)
Expand Down
4 changes: 2 additions & 2 deletions src/App/ConsolePanel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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 [
Expand Down
130 changes: 130 additions & 0 deletions src/App/Helpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,133 @@ module Tooltip =
|> List.fold (fun (res: string) (oldValue, newValue) ->
res.Replace(oldValue, newValue)
) res

[<RequireQualifiedAccess>]
module Translations =
let enTranslations = dict["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 occured 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 successfuly";
"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 occured when loading the gist";
"msg_update_url_failed", "An error occured 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", "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_githubtoken", "Github token";
"msg_options_gist_token_githubtoken_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";
"msg_options_size_small", "Small";
"msg_options_size_small", "Small"]

let translations = dict["en", enTranslations]
let [<Literal>]defaultLanguage = "en"
let selectedLanguage = Browser.Navigator.navigator.language |> Option.defaultValue defaultLanguage
let languageCode = selectedLanguage.Substring(0, 2)
let translation = if translations.ContainsKey languageCode then translations[languageCode] else translations[defaultLanguage]

let msg_iframe_loaded = translation["msg_iframe_loaded"]
let win_header_console = translation["win_header_console"]
let msg_initializing = translation["msg_initializing"]
let msg_repl_name = translation["msg_repl_name"]
let msg_desktop_experience = translation["msg_desktop_experience"]
let btn_continue = translation["btn_continue"]
let msg_gist_description = translation["msg_gist_description"]
let msg_compilation_failed = translation["msg_compilation_failed"]
let msg_assemblies_load_failed = translation["msg_assemblies_load_failed"]
let msg_gist_token_invalid = translation["msg_gist_token_invalid"]
let msg_gist_token_missing = translation["msg_gist_token_missing"]
let msg_compilation_successful = translation["msg_compilation_successful"]
let msg_shareable_url_ready_text = translation["msg_shareable_url_ready_text"]
let msg_shareable_url_ready_title = translation["msg_shareable_url_ready_title"]
let msg_load_gist_error = translation["msg_load_gist_error"]
let msg_update_url_failed = translation["msg_update_url_failed"]
let msg_fatal_error = translation["msg_fatal_error"]
let msg_live_sample_text = translation["msg_live_sample_text"]
let msg_code_text = translation["msg_code_text"]
let msg_problems = translation["msg_problems"]
let msg_problems_info = translation["msg_problems_info"]
let msg_invalid_iframe_error = translation["msg_invalid_iframe_error"]
let msg_collapse_sidebar = translation["msg_collapse_sidebar"]
let msg_widget_general = translation["msg_widget_general"]
let msg_widget_samples = translation["msg_widget_samples"]
let msg_widget_options = translation["msg_widget_options"]
let msg_widget_statistics = translation["msg_widget_statistics"]
let msg_widget_about = translation["msg_widget_about"]
let msg_found_a_bug = translation["msg_found_a_bug"]
let msg_general_compile_run_tooltip = translation["msg_general_compile_run_tooltip"]
let msg_general_compile_run_text = translation["msg_general_compile_run_text"]
let msg_general_refresh_sample_tooltip = translation["msg_general_refresh_sample_tooltip"]
let msg_general_refresh_sample_text = translation["msg_general_refresh_sample_text"]
let msg_general_reset_repl_tooltip = translation["msg_general_reset_repl_tooltip"]
let msg_general_reset_repl_text = translation["msg_general_reset_repl_text"]
let msg_general_share_url_tooltip = translation["msg_general_share_url_tooltip"]
let msg_general_share_url_text = translation["msg_general_share_url_text"]
let msg_general_share_gist_tooltip = translation["msg_general_share_gist_tooltip"]
let msg_general_share_gist_text = translation["msg_general_share_gist_text"]
let msg_reset_confirmation_text = translation["msg_reset_confirmation_text"]
let btn_confirm = translation["btn_confirm"]
let btn_cancel = translation["btn_cancel"]
let btn_save = translation["btn_save"]
let msg_options_editor_font_size = translation["msg_options_editor_font_size"]
let msg_options_size_small = translation["msg_options_size_small"]
let msg_options_size_medium = translation["msg_options_size_medium"]
let msg_options_size_large = translation["msg_options_size_large"]
let msg_options_editor_font_family = translation["msg_options_editor_font_family"]
let msg_options_programming_language = translation["msg_options_programming_language"]
let msg_options_settings_optimize = translation["msg_options_settings_optimize"]
let msg_options_settings_debug = translation["msg_options_settings_debug"]
let msg_options_settings_typed_arrays = translation["msg_options_settings_typed_arrays"]
let msg_options_gist_token_delete = translation["msg_options_gist_token_delete"]
let msg_options_gist_token_githubtoken = translation["msg_options_gist_token_githubtoken"]
let msg_options_gist_token_githubtoken_create = translation["msg_options_gist_token_githubtoken_create"]
let msg_options_gist_token_gist_scope = translation["msg_options_gist_token_gist_scope"]
let msg_samples_refresh_samples = translation["msg_samples_refresh_samples"]
let msg_stats_steps = translation["msg_stats_steps"]
let msg_stats_milliseconds_short = translation["msg_stats_milliseconds_short"]
8 changes: 4 additions & 4 deletions src/App/Loader.fs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ let init (result: Option<Router.Page>) =
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)
Expand All @@ -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
]
]
]
Expand Down
32 changes: 16 additions & 16 deletions src/App/Main.fs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,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
Expand Down Expand Up @@ -216,7 +216,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
Expand Down Expand Up @@ -269,7 +269,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
}
Expand Down Expand Up @@ -335,14 +335,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
Expand Down Expand Up @@ -388,7 +388,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
Expand All @@ -408,7 +408,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
Expand Down Expand Up @@ -563,8 +563,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.)
Expand All @@ -573,15 +573,15 @@ 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

| 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
Expand Down Expand Up @@ -781,12 +781,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)
Expand Down Expand Up @@ -839,7 +839,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)
Expand Down Expand Up @@ -1005,7 +1005,7 @@ let private outputTabs (activeTab : OutputTab) dispatch =
)
prop.children [
Html.a [
prop.text "Live sample"
prop.text Translations.msg_live_sample_text
]
]
]
Expand All @@ -1018,7 +1018,7 @@ let private outputTabs (activeTab : OutputTab) dispatch =
)
prop.children [
Html.a [
prop.text "Code"
prop.text Translations.msg_code_text
]
]
]
Expand Down
5 changes: 4 additions & 1 deletion src/App/Mouse.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 option -> string,unit,string,string>(Translations.msg_invalid_iframe_error)

sprintf formatMessage x
|> Decode.fail
)
Decode.fromValue "$" iframeMessageDecoder ev?data
Expand Down
Loading

0 comments on commit 64ddaa1

Please sign in to comment.