Jelajahi Sumber

Translation infrastructure

Eugene Pankov 4 tahun lalu
induk
melakukan
0814d44207
100 mengubah file dengan 7617 tambahan dan 416 penghapusan
  1. 2 0
      .gitignore
  2. 3 0
      locale/STOP.txt
  3. 196 0
      locale/af.po
  4. 1303 0
      locale/app.pot
  5. 196 0
      locale/ar.po
  6. 196 0
      locale/ca.po
  7. 196 0
      locale/cs.po
  8. 196 0
      locale/da.po
  9. 196 0
      locale/de.po
  10. 196 0
      locale/el.po
  11. 196 0
      locale/en.po
  12. 196 0
      locale/es.po
  13. 196 0
      locale/fi.po
  14. 196 0
      locale/fr.po
  15. 196 0
      locale/he.po
  16. 196 0
      locale/hu.po
  17. 196 0
      locale/it.po
  18. 196 0
      locale/ja.po
  19. 196 0
      locale/ko.po
  20. 196 0
      locale/nl.po
  21. 196 0
      locale/no.po
  22. 196 0
      locale/pl.po
  23. 196 0
      locale/pt.po
  24. 196 0
      locale/ro.po
  25. 196 0
      locale/ru.po
  26. 196 0
      locale/sr.po
  27. 196 0
      locale/sv.po
  28. 196 0
      locale/tr.po
  29. 196 0
      locale/uk.po
  30. 196 0
      locale/vi.po
  31. 196 0
      locale/zh.po
  32. 7 2
      package.json
  33. 28 0
      scripts/i18n-extract.js
  34. 3 0
      tabby-core/package.json
  35. 1 0
      tabby-core/src/api/index.ts
  36. 3 1
      tabby-core/src/buttonProvider.ts
  37. 2 2
      tabby-core/src/components/renameTabModal.component.pug
  38. 2 2
      tabby-core/src/components/safeModeModal.component.pug
  39. 3 3
      tabby-core/src/components/startPage.component.pug
  40. 1 1
      tabby-core/src/components/transfersMenu.component.pug
  41. 10 3
      tabby-core/src/components/transfersMenu.component.ts
  42. 8 3
      tabby-core/src/components/unlockVaultModal.component.pug
  43. 6 6
      tabby-core/src/components/welcomeTab.component.pug
  44. 3 1
      tabby-core/src/components/welcomeTab.component.ts
  45. 1 0
      tabby-core/src/configDefaults.yaml
  46. 47 45
      tabby-core/src/hotkeys.ts
  47. 39 5
      tabby-core/src/index.ts
  48. 3 1
      tabby-core/src/profiles.ts
  49. 13 4
      tabby-core/src/services/config.service.ts
  50. 10 5
      tabby-core/src/services/fileProviders.service.ts
  51. 134 0
      tabby-core/src/services/locale.service.ts
  52. 9 7
      tabby-core/src/services/profiles.service.ts
  53. 26 21
      tabby-core/src/tabContextMenu.ts
  54. 16 3
      tabby-core/src/theme.ts
  55. 8 7
      tabby-core/src/utils.ts
  56. 55 0
      tabby-core/yarn.lock
  57. 5 3
      tabby-electron/src/hotkeys.ts
  58. 3 2
      tabby-electron/src/services/platform.service.ts
  59. 7 3
      tabby-electron/src/services/updater.service.ts
  60. 4 3
      tabby-linkifier/src/decorator.ts
  61. 3 2
      tabby-local/src/buttonProvider.ts
  62. 4 4
      tabby-local/src/components/commandLineEditor.component.pug
  63. 3 3
      tabby-local/src/components/environmentEditor.component.pug
  64. 3 3
      tabby-local/src/components/localProfileSettings.component.pug
  65. 5 5
      tabby-local/src/components/shellSettingsTab.component.pug
  66. 10 3
      tabby-local/src/components/terminalTab.component.ts
  67. 4 2
      tabby-local/src/hotkeys.ts
  68. 3 2
      tabby-local/src/profiles.ts
  69. 3 2
      tabby-local/src/services/dockMenu.service.ts
  70. 4 3
      tabby-local/src/shells/linuxDefault.ts
  71. 3 2
      tabby-local/src/shells/macDefault.ts
  72. 3 2
      tabby-local/src/shells/winDefault.ts
  73. 11 9
      tabby-local/src/tabContextMenu.ts
  74. 18 17
      tabby-plugin-manager/src/components/pluginsSettingsTab.component.pug
  75. 11 11
      tabby-serial/src/components/serialProfileSettings.component.pug
  76. 2 2
      tabby-serial/src/components/serialTab.component.pug
  77. 11 8
      tabby-serial/src/components/serialTab.component.ts
  78. 5 3
      tabby-serial/src/hotkeys.ts
  79. 14 8
      tabby-serial/src/profiles.ts
  80. 3 2
      tabby-settings/src/buttonProvider.ts
  81. 19 19
      tabby-settings/src/components/configSyncSettingsTab.component.pug
  82. 16 9
      tabby-settings/src/components/configSyncSettingsTab.component.ts
  83. 9 9
      tabby-settings/src/components/editProfileModal.component.pug
  84. 2 2
      tabby-settings/src/components/hotkeyInputModal.component.pug
  85. 1 1
      tabby-settings/src/components/hotkeySettingsTab.component.pug
  86. 1 1
      tabby-settings/src/components/multiHotkeyInput.component.pug
  87. 12 12
      tabby-settings/src/components/profilesSettingsTab.component.pug
  88. 24 14
      tabby-settings/src/components/profilesSettingsTab.component.ts
  89. 3 3
      tabby-settings/src/components/releaseNotesTab.component.ts
  90. 4 4
      tabby-settings/src/components/setVaultPassphraseModal.component.pug
  91. 46 31
      tabby-settings/src/components/settingsTab.component.pug
  92. 5 1
      tabby-settings/src/components/settingsTab.component.ts
  93. 17 17
      tabby-settings/src/components/vaultSettingsTab.component.pug
  94. 12 8
      tabby-settings/src/components/vaultSettingsTab.component.ts
  95. 46 46
      tabby-settings/src/components/windowSettingsTab.component.pug
  96. 4 2
      tabby-settings/src/hotkeys.ts
  97. 13 4
      tabby-settings/src/settings.ts
  98. 1 1
      tabby-ssh/src/components/keyboardInteractiveAuthPanel.component.pug
  99. 2 2
      tabby-ssh/src/components/sftpDeleteModal.component.pug
  100. 4 4
      tabby-ssh/src/components/sftpPanel.component.pug

+ 2 - 0
.gitignore

@@ -34,3 +34,5 @@ sentry-symbols.js
 
 
 tabby-ssh/util/pagent.exe
 tabby-ssh/util/pagent.exe
 *.psd
 *.psd
+
+crowdin.yml

+ 3 - 0
locale/STOP.txt

@@ -0,0 +1,3 @@
+Do not submit pull requests for translations.
+
+Translations are managed at https://crowdin.com/project/tabby

+ 196 - 0
locale/af.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: af\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Afrikaans\n"
+"Language: af_ZA\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 1303 - 0
locale/app.pot

@@ -0,0 +1,1303 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "\"{command}\" is still running. Close?"
+msgstr ""
+
+msgid "A second font family used to display characters missing in the main font"
+msgstr ""
+
+msgid "Abort all"
+msgstr ""
+
+msgid "Acrylic background"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Add a private key"
+msgstr ""
+
+msgid "Add..."
+msgstr ""
+
+msgid "Additional space between lines"
+msgstr ""
+
+msgid "Advanced"
+msgstr ""
+
+msgid "Agent"
+msgstr ""
+
+msgid "Agent forwarding"
+msgstr ""
+
+msgid "Agent pipe path"
+msgstr ""
+
+msgid "Agent type"
+msgstr ""
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Appearance"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid ""
+"Are you sure you want to close Tabby? You can disable this prompt in "
+"Settings -> Window."
+msgstr ""
+
+msgid "Are you sure?"
+msgstr ""
+
+msgid "Arguments"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Ask before closing the browser tab"
+msgstr ""
+
+msgid "Authentication method"
+msgstr ""
+
+msgid "Auto"
+msgstr ""
+
+msgid "Automatic"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Background type"
+msgstr ""
+
+msgid "Baud rate"
+msgstr ""
+
+msgid "Beginning of the line"
+msgstr ""
+
+msgid "Blink cursor"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Blur"
+msgstr ""
+
+msgid "Bottom"
+msgstr ""
+
+msgid "Built-in"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Change baud rate"
+msgstr ""
+
+msgid "Change the master passphrase"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Ciphers"
+msgstr ""
+
+msgid "Clear recent profiles"
+msgstr ""
+
+msgid "Clear terminal"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close and never show again"
+msgstr ""
+
+msgid "Close focused pane"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tab"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Color scheme"
+msgstr ""
+
+msgid "Command line"
+msgstr ""
+
+msgid "Command's stdin/stdout is used instead of a network connection"
+msgstr ""
+
+msgid "Compact"
+msgstr ""
+
+msgid "Config downloaded"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Config sync"
+msgstr ""
+
+msgid "Config uploaded"
+msgstr ""
+
+msgid "Connect through a proxy server"
+msgstr ""
+
+msgid "Connect to \"%s\"..."
+msgstr ""
+
+msgid "Connect to a different host first and use it as a proxy"
+msgstr ""
+
+msgid "Connecting"
+msgstr ""
+
+msgid "Connection"
+msgstr ""
+
+msgid "Copied"
+msgstr ""
+
+msgid "Copy"
+msgstr ""
+
+msgid "Copy current path"
+msgstr ""
+
+msgid "Copy to clipboard"
+msgstr ""
+
+msgid "Could not decrypt config"
+msgstr ""
+
+msgid "Current"
+msgstr ""
+
+msgid "Current color scheme"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Cursor shape"
+msgstr ""
+
+msgid "Custom CSS"
+msgstr ""
+
+msgid "Data bits"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Default profile for new tabs"
+msgstr ""
+
+msgid "Default profile settings"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Delete \"{name}\"?"
+msgstr ""
+
+msgid "Delete next word"
+msgstr ""
+
+msgid "Delete previous word"
+msgstr ""
+
+msgid "Delete the group's profiles?"
+msgstr ""
+
+msgid "Delete this script?"
+msgstr ""
+
+msgid "Delete vault contents?"
+msgstr ""
+
+msgid "Delete {fullPath}?"
+msgstr ""
+
+msgid "Deleting"
+msgstr ""
+
+msgid "Device"
+msgstr ""
+
+msgid "Direct"
+msgstr ""
+
+msgid "Disable GPU acceleration"
+msgstr ""
+
+msgid "Disconnect"
+msgstr ""
+
+msgid "Disconnect from {host}?"
+msgstr ""
+
+msgid "Display on"
+msgstr ""
+
+msgid "Do not abort"
+msgstr ""
+
+msgid "Do not close"
+msgstr ""
+
+msgid "Dock always on top"
+msgstr ""
+
+msgid "Dock the terminal"
+msgstr ""
+
+msgid "Docked terminal size"
+msgstr ""
+
+msgid "Docked terminal space"
+msgstr ""
+
+msgid "Docking"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Duplicate as administrator"
+msgstr ""
+
+msgid "Duplicate tab"
+msgstr ""
+
+msgid "Dynamic"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "Enable font ligatures"
+msgstr ""
+
+msgid "Enable global hotkey (Ctrl-Space)"
+msgstr ""
+
+msgid "Enables the experimental Windows ConPTY API"
+msgstr ""
+
+msgid "Encrypt config file"
+msgstr ""
+
+msgid "End of the line"
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "Environment"
+msgstr ""
+
+msgid "Erase config"
+msgstr ""
+
+msgid "Erase the Vault"
+msgstr ""
+
+msgid "Exact match"
+msgstr ""
+
+msgid "Example:"
+msgstr ""
+
+msgid "Export"
+msgstr ""
+
+msgid "Fallback font"
+msgstr ""
+
+msgid "File transfers"
+msgstr ""
+
+msgid "File: {description}"
+msgstr ""
+
+msgid "Fixed"
+msgstr ""
+
+msgid "Focus all panes"
+msgstr ""
+
+msgid "Focus all panes at once (broadcast)"
+msgstr ""
+
+msgid "Focus next pane"
+msgstr ""
+
+msgid "Focus previous pane"
+msgstr ""
+
+msgid "Focus the pane above"
+msgstr ""
+
+msgid "Focus the pane below"
+msgstr ""
+
+msgid "Focus the pane on the left"
+msgstr ""
+
+msgid "Focus the pane on the right"
+msgstr ""
+
+msgid "Font"
+msgstr ""
+
+msgid "Force CR"
+msgstr ""
+
+msgid "Force CRLF"
+msgstr ""
+
+msgid "Force LF"
+msgstr ""
+
+msgid "Forces a specific SSH agent connection type."
+msgstr ""
+
+msgid "Forget"
+msgstr ""
+
+msgid "Forward port"
+msgstr ""
+
+msgid "Forwarded ports"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "From color scheme"
+msgstr ""
+
+msgid "From theme"
+msgstr ""
+
+msgid "Full"
+msgstr ""
+
+msgid "General"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Gives the window a blurred transparent background"
+msgstr ""
+
+msgid "Go up"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Help track the number of Tabby installs across the world!"
+msgstr ""
+
+msgid "Hexadecimal"
+msgstr ""
+
+msgid "Hide dock on blur"
+msgstr ""
+
+msgid "Hide tab close button"
+msgstr ""
+
+msgid "Hide tab index"
+msgstr ""
+
+msgid "Hides the docked terminal when you click away."
+msgstr ""
+
+msgid "Host"
+msgstr ""
+
+msgid "Host key"
+msgstr ""
+
+msgid "Hotkeys"
+msgstr ""
+
+msgid "If disabled, only custom profiles will show up in the profile selector"
+msgstr ""
+
+msgid "Immediately echoes your input locally"
+msgstr ""
+
+msgid "Input is sent as you type"
+msgstr ""
+
+msgid "Input mode"
+msgstr ""
+
+msgid "Input newlines"
+msgstr ""
+
+msgid "Installing the update will close all tabs and restart Tabby."
+msgstr ""
+
+msgid "Intelligent Ctrl-C (copy/abort)"
+msgstr ""
+
+msgid "Interactive"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Jump host"
+msgstr ""
+
+msgid "Jump to next word"
+msgstr ""
+
+msgid "Jump to previous word"
+msgstr ""
+
+msgid "Keep"
+msgstr ""
+
+msgid "Keep Alive Interval (Milliseconds)"
+msgstr ""
+
+msgid "Keep docked terminal always on top"
+msgstr ""
+
+msgid "Key"
+msgstr ""
+
+msgid "Key exchange"
+msgstr ""
+
+msgid "Keyboard-interactive auth"
+msgstr ""
+
+msgid "Kill"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Launch WinSCP"
+msgstr ""
+
+msgid "Launch WinSCP for current SSH session"
+msgstr ""
+
+msgid "Learn how to allow Tabby to detect remote shell's working directory."
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "Line by line"
+msgstr ""
+
+msgid "Line editor, input is sent after you press Enter"
+msgstr ""
+
+msgid "Line padding"
+msgstr ""
+
+msgid "Loading"
+msgstr ""
+
+msgid "Local"
+msgstr ""
+
+msgid "Local echo"
+msgstr ""
+
+msgid "Local terminal"
+msgstr ""
+
+msgid "Login scripts"
+msgstr ""
+
+msgid "Manage profiles"
+msgstr ""
+
+msgid "Max Keep Alive Count"
+msgstr ""
+
+msgid "Maximize the active pane"
+msgstr ""
+
+msgid "Move tab to the left"
+msgstr ""
+
+msgid "Move tab to the right"
+msgstr ""
+
+msgid "Move to \"Ungrouped\""
+msgstr ""
+
+msgid "Name"
+msgstr ""
+
+msgid "Name for the new config"
+msgstr ""
+
+msgid "Named pipe"
+msgstr ""
+
+msgid "Native"
+msgstr ""
+
+msgid "New admin tab"
+msgstr ""
+
+msgid "New config on {platform}"
+msgstr ""
+
+msgid "New item"
+msgstr ""
+
+msgid "New name"
+msgstr ""
+
+msgid "New profile"
+msgstr ""
+
+msgid "New profile name"
+msgstr ""
+
+msgid "New tab"
+msgstr ""
+
+msgid "New terminal"
+msgstr ""
+
+msgid "New window"
+msgstr ""
+
+msgid "New with profile"
+msgstr ""
+
+msgid "Next tab"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "None"
+msgstr ""
+
+msgid "Normal"
+msgstr ""
+
+msgid "Not found"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "OS default"
+msgstr ""
+
+msgid "OS default ({name})"
+msgstr ""
+
+msgid "Off"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Opacity"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Open SFTP panel"
+msgstr ""
+
+msgid "Open Settings"
+msgstr ""
+
+msgid "Optional"
+msgstr ""
+
+msgid "Options"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Output is shown as a hexdump"
+msgstr ""
+
+msgid "Output is shown as it is received"
+msgstr ""
+
+msgid "Output mode"
+msgstr ""
+
+msgid "Output newlines"
+msgstr ""
+
+msgid "Override X11 display"
+msgstr ""
+
+msgid "Overwrite local and sync"
+msgstr ""
+
+msgid "Overwrite remote and sync"
+msgstr ""
+
+msgid "Overwrite the config on the remote side and start syncing?"
+msgstr ""
+
+msgid "Overwrite the local config and start syncing?"
+msgstr ""
+
+msgid "Paper"
+msgstr ""
+
+msgid "Parity"
+msgstr ""
+
+msgid "Passphrase for a private key with hash {hash}..."
+msgstr ""
+
+msgid "Password"
+msgstr ""
+
+msgid "Paste"
+msgstr ""
+
+msgid "Paste from clipboard"
+msgstr ""
+
+msgid "Paste multiple lines?"
+msgstr ""
+
+msgid "Path or address of the local X11 socket"
+msgstr ""
+
+msgid "Pin"
+msgstr ""
+
+msgid "Port"
+msgstr ""
+
+msgid "Port opened"
+msgstr ""
+
+msgid "Ports"
+msgstr ""
+
+msgid "Press any key to reconnect"
+msgstr ""
+
+msgid "Press the key now"
+msgstr ""
+
+msgid "Prevents accidental closing"
+msgstr ""
+
+msgid "Previous tab"
+msgstr ""
+
+msgid "Private keys"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Profiles"
+msgstr ""
+
+msgid "Profiles & connections"
+msgstr ""
+
+msgid "Profiles and connections"
+msgstr ""
+
+msgid "Program"
+msgstr ""
+
+msgid "Proxy command"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Puts all of Tabby's configuration into the vault"
+msgstr ""
+
+msgid "Quick connect"
+msgstr ""
+
+msgid "Quit"
+msgstr ""
+
+msgid "Raw socket connection"
+msgstr ""
+
+msgid "Ready Timeout (Milliseconds)"
+msgstr ""
+
+msgid "Recent"
+msgstr ""
+
+msgid "Reconnect"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Regex"
+msgstr ""
+
+msgid "Release notes"
+msgstr ""
+
+msgid "Remote"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Rename Tab"
+msgstr ""
+
+msgid "Reopen last tab"
+msgstr ""
+
+msgid "Replace"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Reset zoom"
+msgstr ""
+
+msgid "Restart current SSH session"
+msgstr ""
+
+msgid "Restart current Telnet session"
+msgstr ""
+
+msgid "Restart current serial session"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Reuse session for multiple tabs"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Run as administrator"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "SFTP"
+msgstr ""
+
+msgid "SOCKS proxy"
+msgstr ""
+
+msgid "SOCKS proxy host"
+msgstr ""
+
+msgid "SOCKS proxy port"
+msgstr ""
+
+msgid "SSH connection"
+msgstr ""
+
+msgid "SSH connection management is now done through the"
+msgstr ""
+
+msgid "SSH password for {user}@{host}:{port}"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Save a password in the keychain"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save as profile"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Saved"
+msgstr ""
+
+msgid "Saved layout"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select a base profile to use as a template"
+msgstr ""
+
+msgid "Select file storage"
+msgstr ""
+
+msgid "Select profile"
+msgstr ""
+
+msgid "Select profile or enter an address"
+msgstr ""
+
+msgid "Send bytes by typing in hex values"
+msgstr ""
+
+msgid "Sends data one byte at a time"
+msgstr ""
+
+msgid "Serial"
+msgstr ""
+
+msgid "Serial connection"
+msgstr ""
+
+msgid "Serial: {description}"
+msgstr ""
+
+msgid "Set master passphrase"
+msgstr ""
+
+msgid "Set passphrase"
+msgstr ""
+
+msgid "Set password"
+msgstr ""
+
+msgid "Set to 0 to disable recent profiles"
+msgstr ""
+
+msgid "Sets the SSH agent's named pipe path."
+msgstr ""
+
+msgid "Settings"
+msgstr ""
+
+msgid "Shell"
+msgstr ""
+
+msgid "Shell does not support current path detection"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show Serial connections"
+msgstr ""
+
+msgid "Show built-in profiles in selector"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show pane labels (for rearranging)"
+msgstr ""
+
+msgid "Show profile selector"
+msgstr ""
+
+msgid "Show recent profiles in selector"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Show vault contents"
+msgstr ""
+
+msgid "Skip MoTD/banner"
+msgstr ""
+
+msgid "Slow feed"
+msgstr ""
+
+msgid "Snaps the window to a side of the screen"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Split to the bottom"
+msgstr ""
+
+msgid "Split to the left"
+msgstr ""
+
+msgid "Split to the right"
+msgstr ""
+
+msgid "Split to the top"
+msgstr ""
+
+msgid "Standard"
+msgstr ""
+
+msgid "Stop bits"
+msgstr ""
+
+msgid "Strip"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Substitutions allowed."
+msgstr ""
+
+msgid "Switch profile"
+msgstr ""
+
+msgid "Switch profile in the active pane"
+msgstr ""
+
+msgid "Tab 1"
+msgstr ""
+
+msgid "Tab 10"
+msgstr ""
+
+msgid "Tab 11"
+msgstr ""
+
+msgid "Tab 12"
+msgstr ""
+
+msgid "Tab 13"
+msgstr ""
+
+msgid "Tab 14"
+msgstr ""
+
+msgid "Tab 15"
+msgstr ""
+
+msgid "Tab 16"
+msgstr ""
+
+msgid "Tab 17"
+msgstr ""
+
+msgid "Tab 18"
+msgstr ""
+
+msgid "Tab 19"
+msgstr ""
+
+msgid "Tab 2"
+msgstr ""
+
+msgid "Tab 20"
+msgstr ""
+
+msgid "Tab 3"
+msgstr ""
+
+msgid "Tab 4"
+msgstr ""
+
+msgid "Tab 5"
+msgstr ""
+
+msgid "Tab 6"
+msgstr ""
+
+msgid "Tab 7"
+msgstr ""
+
+msgid "Tab 8"
+msgstr ""
+
+msgid "Tab 9"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid ""
+"Tabby could not start with your plugins, so all third party plugins have "
+"been disabled in this session. The error was:"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Tabs"
+msgstr ""
+
+msgid "Tabs location"
+msgstr ""
+
+msgid "Tabs width"
+msgstr ""
+
+msgid "Telnet"
+msgstr ""
+
+msgid "Telnet session"
+msgstr ""
+
+msgid "Terminal"
+msgstr ""
+
+msgid "Terminal background"
+msgstr ""
+
+msgid "Thank you for downloading Tabby!"
+msgstr ""
+
+msgid "Theme"
+msgstr ""
+
+msgid "There are active file transfers"
+msgstr ""
+
+msgid "There is a saved password for this connection"
+msgstr ""
+
+msgid "These apply to all profiles of a given type"
+msgstr ""
+
+msgid "Thin"
+msgstr ""
+
+msgid "Tick this if you're experiencing aliasing, ghosting or other visual issues"
+msgstr ""
+
+msgid "Toggle fullscreen mode"
+msgstr ""
+
+msgid "Toggle last tab"
+msgstr ""
+
+msgid "Toggle terminal window"
+msgstr ""
+
+msgid "Toggles the Tabby window visibility"
+msgstr ""
+
+msgid "Top"
+msgstr ""
+
+msgid "Try again"
+msgstr ""
+
+msgid "Trying saved password"
+msgstr ""
+
+msgid "Ungrouped"
+msgstr ""
+
+msgid "Unknown"
+msgstr ""
+
+msgid "Unknown secret of type {type} for {key}"
+msgstr ""
+
+msgid "Unpin"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "Upload"
+msgstr ""
+
+msgid "Use ConPTY"
+msgstr ""
+
+msgid "User default"
+msgstr ""
+
+msgid "Username"
+msgstr ""
+
+msgid "Using preset password"
+msgstr ""
+
+msgid "Vault"
+msgstr ""
+
+msgid ""
+"Vault is an always-encrypted container for secrets such as SSH passwords "
+"and private key passphrases."
+msgstr ""
+
+msgid "Vault is empty"
+msgstr ""
+
+msgid "Vault is locked"
+msgstr ""
+
+msgid "Vault is not configured"
+msgstr ""
+
+msgid "Vault master passphrase needs to be set to allow storing secrets"
+msgstr ""
+
+msgid "Vibrancy"
+msgstr ""
+
+msgid "WSL terminal only supports TrueColor with ConPTY"
+msgstr ""
+
+msgid "Warn when closing active connections"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "Welcome"
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid ""
+"When WinSCP is detected, you can launch an SCP session from the context "
+"menu."
+msgstr ""
+
+msgid "Whether a custom window or an OS native window should be used"
+msgstr ""
+
+msgid "WinSCP path"
+msgstr ""
+
+msgid "Window"
+msgstr ""
+
+msgid "Window frame"
+msgstr ""
+
+msgid "Windows 10 build 18309 or above is recommended for ConPTY"
+msgstr ""
+
+msgid "Working directory"
+msgstr ""
+
+msgid "Working directory detection"
+msgstr ""
+
+msgid "X11 forwarding"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "You can change it later, but it's unrecoverable if forgotten."
+msgstr ""
+
+msgid "Zoom in"
+msgstr ""
+
+msgid "Zoom out"
+msgstr ""
+
+msgid "click"
+msgstr ""
+
+msgid "tab"
+msgstr ""
+
+msgid "{name} copy"
+msgstr ""

+ 196 - 0
locale/ar.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: ar\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Arabic\n"
+"Language: ar_SA\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/ca.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: ca\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Catalan\n"
+"Language: ca_ES\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/cs.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: cs\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Czech\n"
+"Language: cs_CZ\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/da.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: da\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Danish\n"
+"Language: da_DK\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/de.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: de\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: German\n"
+"Language: de_DE\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/el.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: el\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Greek\n"
+"Language: el_GR\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/en.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: en\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: English\n"
+"Language: en_US\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/es.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: es-ES\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Spanish\n"
+"Language: es_ES\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/fi.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: fi\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Finnish\n"
+"Language: fi_FI\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/fr.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: fr\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: French\n"
+"Language: fr_FR\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/he.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: he\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Hebrew\n"
+"Language: he_IL\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/hu.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: hu\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Hungarian\n"
+"Language: hu_HU\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/it.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: it\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Italian\n"
+"Language: it_IT\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/ja.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: ja\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Japanese\n"
+"Language: ja_JP\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/ko.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: ko\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Korean\n"
+"Language: ko_KR\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/nl.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: nl\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Dutch\n"
+"Language: nl_NL\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/no.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: no\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Norwegian\n"
+"Language: no_NO\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/pl.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: pl\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Polish\n"
+"Language: pl_PL\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/pt.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: pt-BR\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Portuguese, Brazilian\n"
+"Language: pt_BR\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/ro.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: ro\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Romanian\n"
+"Language: ro_RO\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/ru.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: ru\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Russian\n"
+"Language: ru_RU\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr "Приложение"
+
+msgid "Application settings"
+msgstr "Настройки приложения"
+
+msgid "Ask a question"
+msgstr "Задать вопрос"
+
+msgid "Automatic Updates"
+msgstr "Автоматическое обновление"
+
+msgid "Blue"
+msgstr "Синий"
+
+msgid "Check for updates"
+msgstr "Проверить обновления"
+
+msgid "Close"
+msgstr "Закрыть"
+
+msgid "Close other tabs"
+msgstr "Закрыть другие вкладки"
+
+msgid "Close tabs to the left"
+msgstr "Закрыть вкладки слева"
+
+msgid "Close tabs to the right"
+msgstr "Закрыть вкладки справа"
+
+msgid "Color"
+msgstr "Цвет"
+
+msgid "Config file"
+msgstr "Файл настроек"
+
+msgid "Current process: {name}"
+msgstr "Текущий процесс: {name}"
+
+msgid "Debugging"
+msgstr "Отладка"
+
+msgid "Defaults"
+msgstr "Значения по умолчанию"
+
+msgid "Down"
+msgstr "Вниз"
+
+msgid "Duplicate"
+msgstr "Дублировать"
+
+msgid "Enable analytics"
+msgstr "Аналитика"
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr "Включить автоматическую установку обновлений, когда они доступны."
+
+msgid "English"
+msgstr "Английский"
+
+msgid "French"
+msgstr "Французский"
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr "Открыть пред-заполненный отчет на GitHub"
+
+msgid "German"
+msgstr "Немецкий"
+
+msgid "Green"
+msgstr "Зеленый"
+
+msgid "Invalid syntax"
+msgstr "Неверный синтаксис"
+
+msgid "Language"
+msgstr "Язык"
+
+msgid "Left"
+msgstr "Налево"
+
+msgid "No color"
+msgstr "Без цвета"
+
+msgid "Notify on activity"
+msgstr "Уведомить об активности"
+
+msgid "Notify when done"
+msgstr "Уведомить о завершении"
+
+msgid "On GitHub Discussions"
+msgstr "На GitHub Discussions"
+
+msgid "Open DevTools"
+msgstr "Открыть DevTools"
+
+msgid "Orange"
+msgstr "Оранжевый"
+
+msgid "Process completed"
+msgstr "Процесс завершен"
+
+msgid "Profile name"
+msgstr "Название профиля"
+
+msgid "Purple"
+msgstr "Фиолетовый"
+
+msgid "Red"
+msgstr "Красный"
+
+msgid "Rename"
+msgstr "Переименовать"
+
+msgid "Report a problem"
+msgstr "Сообщить о проблеме"
+
+msgid "Restart the app to apply changes"
+msgstr "Перезапустите приложение, чтобы применить изменения"
+
+msgid "Right"
+msgstr "Направо"
+
+msgid "Russian"
+msgstr "Русский"
+
+msgid "Save and apply"
+msgstr "Сохранить и применить"
+
+msgid "Save layout as profile"
+msgstr "Сохранить как профиль"
+
+msgid "Shell integration"
+msgstr "Интеграция в систему"
+
+msgid "Show config file"
+msgstr "Показать файл настроек"
+
+msgid "Show defaults"
+msgstr "Показать значения по умолчанию"
+
+msgid "Show release notes"
+msgstr "Посмотреть изменения в релизе"
+
+msgid "Source code"
+msgstr "Исходный код"
+
+msgid "Split"
+msgstr "Разделить"
+
+msgid "Subscribe to updates"
+msgstr "Подпишитесь на обновления"
+
+msgid "Tab activity"
+msgstr "Активность в вкладке"
+
+msgid "Tabby news and updates on Twitter"
+msgstr "Новости и обновления про Tabby в Twitter"
+
+msgid "Up"
+msgstr "Вверх"
+
+msgid "Update"
+msgstr "Обновить"
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr "Отслеживаем только версию ОС и приложения."
+
+msgid "What's new"
+msgstr "Что нового"
+
+msgid "Yellow"
+msgstr "Желтый"
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/sr.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: sr\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Serbian (Cyrillic)\n"
+"Language: sr_SP\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/sv.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: sv-SE\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Swedish\n"
+"Language: sv_SE\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/tr.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: tr\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Turkish\n"
+"Language: tr_TR\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/uk.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: uk\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Ukrainian\n"
+"Language: uk_UA\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/vi.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: vi\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Vietnamese\n"
+"Language: vi_VN\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 196 - 0
locale/zh.po

@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"mime-version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Crowdin-Project: tabby\n"
+"X-Crowdin-Project-ID: 493349\n"
+"X-Crowdin-Language: zh-TW\n"
+"X-Crowdin-File: /locale/app.pot\n"
+"X-Crowdin-File-ID: 75\n"
+"Project-Id-Version: tabby\n"
+"Language-Team: Chinese Traditional\n"
+"Language: zh_TW\n"
+"PO-Revision-Date: 2022-01-08 12:42\n"
+
+msgid "Allows quickly opening a terminal in the selected folder"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application settings"
+msgstr ""
+
+msgid "Ask a question"
+msgstr ""
+
+msgid "Automatic Updates"
+msgstr ""
+
+msgid "Blue"
+msgstr ""
+
+msgid "Check for updates"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Close other tabs"
+msgstr ""
+
+msgid "Close tabs to the left"
+msgstr ""
+
+msgid "Close tabs to the right"
+msgstr ""
+
+msgid "Color"
+msgstr ""
+
+msgid "Config file"
+msgstr ""
+
+msgid "Current process: {name}"
+msgstr ""
+
+msgid "Debugging"
+msgstr ""
+
+msgid "Defaults"
+msgstr ""
+
+msgid "Down"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Enable analytics"
+msgstr ""
+
+msgid "Enable automatic installation of updates when they become available."
+msgstr ""
+
+msgid "English"
+msgstr ""
+
+msgid "French"
+msgstr ""
+
+msgid "Generate a pre-filled GitHub issue"
+msgstr ""
+
+msgid "German"
+msgstr ""
+
+msgid "Green"
+msgstr ""
+
+msgid "Invalid syntax"
+msgstr ""
+
+msgid "Language"
+msgstr ""
+
+msgid "Left"
+msgstr ""
+
+msgid "No color"
+msgstr ""
+
+msgid "Notify on activity"
+msgstr ""
+
+msgid "Notify when done"
+msgstr ""
+
+msgid "On GitHub Discussions"
+msgstr ""
+
+msgid "Open DevTools"
+msgstr ""
+
+msgid "Orange"
+msgstr ""
+
+msgid "Process completed"
+msgstr ""
+
+msgid "Profile name"
+msgstr ""
+
+msgid "Purple"
+msgstr ""
+
+msgid "Red"
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Report a problem"
+msgstr ""
+
+msgid "Restart the app to apply changes"
+msgstr ""
+
+msgid "Right"
+msgstr ""
+
+msgid "Russian"
+msgstr ""
+
+msgid "Save and apply"
+msgstr ""
+
+msgid "Save layout as profile"
+msgstr ""
+
+msgid "Shell integration"
+msgstr ""
+
+msgid "Show config file"
+msgstr ""
+
+msgid "Show defaults"
+msgstr ""
+
+msgid "Show release notes"
+msgstr ""
+
+msgid "Source code"
+msgstr ""
+
+msgid "Split"
+msgstr ""
+
+msgid "Subscribe to updates"
+msgstr ""
+
+msgid "Tab activity"
+msgstr ""
+
+msgid "Tabby news and updates on Twitter"
+msgstr ""
+
+msgid "Up"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "We're only tracking your Tabby and OS versions."
+msgstr ""
+
+msgid "What's new"
+msgstr ""
+
+msgid "Yellow"
+msgstr ""
+
+msgid "click"
+msgstr ""
+

+ 7 - 2
package.json

@@ -7,6 +7,8 @@
     "@angular/forms": "^12.0.0",
     "@angular/forms": "^12.0.0",
     "@angular/platform-browser": "^12.0.0",
     "@angular/platform-browser": "^12.0.0",
     "@angular/platform-browser-dynamic": "^12.0.0",
     "@angular/platform-browser-dynamic": "^12.0.0",
+    "@biesbjerg/ngx-translate-extract": "^7.0.4",
+    "@biesbjerg/ngx-translate-extract-marker": "^1.0.0",
     "@fortawesome/fontawesome-free": "^6.0.0-beta3",
     "@fortawesome/fontawesome-free": "^6.0.0-beta3",
     "@ng-bootstrap/ng-bootstrap": "^10.0.0",
     "@ng-bootstrap/ng-bootstrap": "^10.0.0",
     "@sentry/cli": "^1.71.0",
     "@sentry/cli": "^1.71.0",
@@ -40,7 +42,7 @@
     "file-loader": "^6.2.0",
     "file-loader": "^6.2.0",
     "graceful-fs": "^4.2.8",
     "graceful-fs": "^4.2.8",
     "html-loader": "2.1.2",
     "html-loader": "2.1.2",
-    "json-loader": "0.5.7",
+    "json-loader": "^0.5.7",
     "lru-cache": "^6.0.0",
     "lru-cache": "^6.0.0",
     "macos-release": "^3.0.1",
     "macos-release": "^3.0.1",
     "ngx-sortablejs": "^11.1.0",
     "ngx-sortablejs": "^11.1.0",
@@ -50,7 +52,9 @@
     "npmlog": "6.0.0",
     "npmlog": "6.0.0",
     "npx": "^10.2.2",
     "npx": "^10.2.2",
     "patch-package": "^6.4.7",
     "patch-package": "^6.4.7",
-    "pug": "^3.0.2",
+    "po-gettext-loader": "^1.0.0",
+    "pug": "3",
+    "pug-cli": "^1.0.0-alpha6",
     "pug-html-loader": "1.1.5",
     "pug-html-loader": "1.1.5",
     "pug-lint": "^2.6.0",
     "pug-lint": "^2.6.0",
     "pug-loader": "^2.4.0",
     "pug-loader": "^2.4.0",
@@ -80,6 +84,7 @@
     "zone.js": "^0.11.4"
     "zone.js": "^0.11.4"
   },
   },
   "resolutions": {
   "resolutions": {
+    "*/pug": "^3",
     "lzma-native": "^8.0.0",
     "lzma-native": "^8.0.0",
     "*/node-abi": "^3.5.0",
     "*/node-abi": "^3.5.0",
     "**/graceful-fs": "^4.2.4"
     "**/graceful-fs": "^4.2.4"

+ 28 - 0
scripts/i18n-extract.js

@@ -0,0 +1,28 @@
+#!/usr/bin/env node
+const sh = require('shelljs')
+const fs = require('fs/promises')
+const vars = require('./vars')
+const log = require('npmlog')
+
+const tempOutput = 'locale/app.new.pot'
+const pot = 'locale/app.pot'
+const tempHtml = 'locale/tmp-html'
+
+;(async () => {
+    sh.mkdir('-p', tempHtml)
+    for (const plugin of vars.builtinPlugins) {
+        log.info('extract-pug', plugin)
+
+        sh.exec(`yarn pug --doctype html -s --pretty -O '{require: function(){}}' -o ${tempHtml}/${plugin} ${plugin}`)
+
+        log.info('extract-ts', plugin)
+        sh.exec(`node node_modules/.bin/ngx-translate-extract -i ${plugin}/src -m -s -f pot -o ${tempOutput}`)
+
+    }
+
+    log.info('extract-pug')
+    sh.exec(`node node_modules/.bin/ngx-translate-extract -i ${tempHtml} -f pot -s -o ${tempOutput}`)
+
+    sh.rm('-r', tempHtml)
+    await fs.rename(tempOutput, pot)
+})()

+ 3 - 0
tabby-core/package.json

@@ -17,12 +17,15 @@
   "author": "Eugene Pankov",
   "author": "Eugene Pankov",
   "license": "MIT",
   "license": "MIT",
   "devDependencies": {
   "devDependencies": {
+    "@ngx-translate/core": "^14.0.0",
     "bootstrap": "^4.1.3",
     "bootstrap": "^4.1.3",
     "deepmerge": "^4.1.1",
     "deepmerge": "^4.1.1",
     "js-yaml": "^4.0.0",
     "js-yaml": "^4.0.0",
+    "messageformat": "^2.3.0",
     "mixpanel": "^0.13.0",
     "mixpanel": "^0.13.0",
     "ngx-filesize": "^2.0.16",
     "ngx-filesize": "^2.0.16",
     "ngx-perfect-scrollbar": "^10.1.0",
     "ngx-perfect-scrollbar": "^10.1.0",
+    "ngx-translate-messageformat-compiler": "^4.11.0",
     "readable-stream": "3.6.0",
     "readable-stream": "3.6.0",
     "uuid": "^8.0.0"
     "uuid": "^8.0.0"
   },
   },

+ 1 - 0
tabby-core/src/api/index.ts

@@ -35,4 +35,5 @@ export { TabsService, NewTabParameters, TabComponentType } from '../services/tab
 export { UpdaterService } from '../services/updater.service'
 export { UpdaterService } from '../services/updater.service'
 export { VaultService, Vault, VaultSecret, VaultFileSecret, VAULT_SECRET_TYPE_FILE, StoredVault, VaultSecretKey } from '../services/vault.service'
 export { VaultService, Vault, VaultSecret, VaultFileSecret, VAULT_SECRET_TYPE_FILE, StoredVault, VaultSecretKey } from '../services/vault.service'
 export { FileProvidersService } from '../services/fileProviders.service'
 export { FileProvidersService } from '../services/fileProviders.service'
+export { LocaleService, TranslateServiceWrapper as TranslateService } from '../services/locale.service'
 export * from '../utils'
 export * from '../utils'

+ 3 - 1
tabby-core/src/buttonProvider.ts

@@ -1,5 +1,6 @@
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
+import { TranslateService } from '@ngx-translate/core'
 
 
 import { ToolbarButton, ToolbarButtonProvider } from './api/toolbarButtonProvider'
 import { ToolbarButton, ToolbarButtonProvider } from './api/toolbarButtonProvider'
 import { HostAppService, Platform } from './api/hostApp'
 import { HostAppService, Platform } from './api/hostApp'
@@ -12,6 +13,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
     constructor (
     constructor (
         private hostApp: HostAppService,
         private hostApp: HostAppService,
         private profilesService: ProfilesService,
         private profilesService: ProfilesService,
+        private translate: TranslateService,
         hotkeys: HotkeysService,
         hotkeys: HotkeysService,
     ) {
     ) {
         super()
         super()
@@ -35,7 +37,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
                 icon: this.hostApp.platform === Platform.Web
                 icon: this.hostApp.platform === Platform.Web
                     ? require('./icons/plus.svg')
                     ? require('./icons/plus.svg')
                     : require('./icons/profiles.svg'),
                     : require('./icons/profiles.svg'),
-                title: 'Profiles and connections',
+                title: this.translate.instant('Profiles and connections'),
                 click: () => this.activate(),
                 click: () => this.activate(),
             },
             },
             ...this.profilesService.getRecentProfiles().map(profile => ({
             ...this.profilesService.getRecentProfiles().map(profile => ({

+ 2 - 2
tabby-core/src/components/renameTabModal.component.pug

@@ -2,5 +2,5 @@
     input.form-control(type='text', #input, [(ngModel)]='value', (keyup.enter)='save()', autofocus)
     input.form-control(type='text', #input, [(ngModel)]='value', (keyup.enter)='save()', autofocus)
 
 
 .modal-footer
 .modal-footer
-    button.btn.btn-primary((click)='save()') Save
-    button.btn.btn-secondary((click)='close()') Cancel
+    button.btn.btn-primary((click)='save()', translate) Save
+    button.btn.btn-secondary((click)='close()', translate) Cancel

+ 2 - 2
tabby-core/src/components/safeModeModal.component.pug

@@ -1,7 +1,7 @@
 .modal-body
 .modal-body
-    .alert.alert-danger Tabby could not start with your plugins, so all third party plugins have been disabled in this session. The error was:
+    .alert.alert-danger(translate) Tabby could not start with your plugins, so all third party plugins have been disabled in this session. The error was:
 
 
     pre {{error}}
     pre {{error}}
 
 
 .modal-footer
 .modal-footer
-    button.btn.btn-primary((click)='close()') Close
+    button.btn.btn-primary((click)='close()', translate) Close

+ 3 - 3
tabby-core/src/components/startPage.component.pug

@@ -15,9 +15,9 @@ footer.d-flex.align-items-center
     .btn-group.mr-auto
     .btn-group.mr-auto
         button.btn.btn-dark((click)='homeBase.openGitHub()')
         button.btn.btn-dark((click)='homeBase.openGitHub()')
             i.fab.fa-github
             i.fab.fa-github
-            span GitHub
+            span(translate) GitHub
         button.btn.btn-dark((click)='homeBase.reportBug()')
         button.btn.btn-dark((click)='homeBase.reportBug()')
             i.fas.fa-bug
             i.fas.fa-bug
-            span Report a problem
+            span(translate) Report a problem
 
 
-    .form-control-static.selectable.no-drag Version: {{homeBase.appVersion}}
+    .form-control-static.selectable.no-drag(translate, [translateParams]='{version: homeBase.appVersion}') Version: {version}

+ 1 - 1
tabby-core/src/components/transfersMenu.component.pug

@@ -1,5 +1,5 @@
 .d-flex.align-items-center
 .d-flex.align-items-center
-    .dropdown-header File transfers
+    .dropdown-header(translate) File transfers
     button.btn.btn-link.ml-auto((click)='removeAll(); $event.stopPropagation()') !{require('../icons/times.svg')}
     button.btn.btn-link.ml-auto((click)='removeAll(); $event.stopPropagation()') !{require('../icons/times.svg')}
 .transfer(*ngFor='let transfer of transfers', (click)='showTransfer(transfer)')
 .transfer(*ngFor='let transfer of transfers', (click)='showTransfer(transfer)')
     .icon(*ngIf='isDownload(transfer)') !{require('../icons/download.svg')}
     .icon(*ngIf='isDownload(transfer)') !{require('../icons/download.svg')}

+ 10 - 3
tabby-core/src/components/transfersMenu.component.ts

@@ -1,4 +1,5 @@
 import { Component, Input, Output, EventEmitter } from '@angular/core'
 import { Component, Input, Output, EventEmitter } from '@angular/core'
+import { TranslateService } from '@ngx-translate/core'
 import { FileDownload, FileTransfer, PlatformService } from '../api/platform'
 import { FileDownload, FileTransfer, PlatformService } from '../api/platform'
 
 
 /** @hidden */
 /** @hidden */
@@ -11,7 +12,10 @@ export class TransfersMenuComponent {
     @Input() transfers: FileTransfer[]
     @Input() transfers: FileTransfer[]
     @Output() transfersChange = new EventEmitter<FileTransfer[]>()
     @Output() transfersChange = new EventEmitter<FileTransfer[]>()
 
 
-    constructor (private platform: PlatformService) { }
+    constructor (
+        private platform: PlatformService,
+        private translate: TranslateService,
+    ) { }
 
 
     isDownload (transfer: FileTransfer): boolean {
     isDownload (transfer: FileTransfer): boolean {
         return transfer instanceof FileDownload
         return transfer instanceof FileDownload
@@ -40,8 +44,11 @@ export class TransfersMenuComponent {
         if (this.transfers.some(x => !x.isComplete())) {
         if (this.transfers.some(x => !x.isComplete())) {
             if ((await this.platform.showMessageBox({
             if ((await this.platform.showMessageBox({
                 type: 'warning',
                 type: 'warning',
-                message: 'There are active file transfers',
-                buttons: ['Abort all', 'Do not abort'],
+                message: this.translate.instant('There are active file transfers'),
+                buttons: [
+                    this.translate.instant('Abort all'),
+                    this.translate.instant('Do not abort'),
+                ],
                 defaultId: 1,
                 defaultId: 1,
                 cancelId: 1,
                 cancelId: 1,
             })).response === 1) {
             })).response === 1) {

+ 8 - 3
tabby-core/src/components/unlockVaultModal.component.pug

@@ -1,13 +1,18 @@
 .modal-body
 .modal-body
     .d-flex.align-items-center.mb-3
     .d-flex.align-items-center.mb-3
-        h3.m-0 Vault is locked
+        h3.m-0(translate) Vault is locked
         .ml-auto(ngbDropdown, placement='bottom-right')
         .ml-auto(ngbDropdown, placement='bottom-right')
             button.btn.btn-link(ngbDropdownToggle, (click)='$event.stopPropagation()')
             button.btn.btn-link(ngbDropdownToggle, (click)='$event.stopPropagation()')
-                span(*ngIf='rememberFor') Remember for {{getRememberForDisplay(rememberFor)}}
-                span(*ngIf='!rememberFor') Do not remember
+                span(
+                    *ngIf='rememberFor',
+                    translate,
+                    [translateParams]='{time: getRememberForDisplay(rememberFor)}'
+                ) Remember for {time}
+                span(*ngIf='!rememberFor', translate) Do not remember
             div(ngbDropdownMenu)
             div(ngbDropdownMenu)
                 button.dropdown-item(
                 button.dropdown-item(
                     (click)='rememberFor = 0',
                     (click)='rememberFor = 0',
+                    translate
                 ) Do not remember
                 ) Do not remember
                 button.dropdown-item(
                 button.dropdown-item(
                     *ngFor='let x of rememberOptions',
                     *ngFor='let x of rememberOptions',

+ 6 - 6
tabby-core/src/components/welcomeTab.component.pug

@@ -4,21 +4,21 @@
         h1.tabby-title Tabby
         h1.tabby-title Tabby
             sup α
             sup α
 
 
-    .text-center.mb-5 Thank you for downloading Tabby!
+    .text-center.mb-5(translate) Thank you for downloading Tabby!
 
 
     .form-line
     .form-line
         .header
         .header
-            .title Enable analytics
-            .description Help track the number of Tabby installs across the world!
+            .title(translate) Enable analytics
+            .description(translate) Help track the number of Tabby installs across the world!
         toggle([(ngModel)]='config.store.enableAnalytics')
         toggle([(ngModel)]='config.store.enableAnalytics')
 
 
 
 
     .form-line
     .form-line
         .header
         .header
-            .title Enable global hotkey (#[strong Ctrl-Space])
-            .description Toggles the Tabby window visibility
+            .title(translate) Enable global hotkey (Ctrl-Space)
+            .description(translate) Toggles the Tabby window visibility
         toggle([(ngModel)]='enableGlobalHotkey')
         toggle([(ngModel)]='enableGlobalHotkey')
 
 
 
 
     .text-center.mt-5
     .text-center.mt-5
-        button.btn.btn-primary((click)='closeAndDisable()') Close and never show again
+        button.btn.btn-primary((click)='closeAndDisable()', translate) Close and never show again

+ 3 - 1
tabby-core/src/components/welcomeTab.component.ts

@@ -1,5 +1,6 @@
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 import { Component } from '@angular/core'
 import { Component } from '@angular/core'
+import { TranslateService } from '@ngx-translate/core'
 import { BaseTabComponent } from './baseTab.component'
 import { BaseTabComponent } from './baseTab.component'
 import { ConfigService } from '../services/config.service'
 import { ConfigService } from '../services/config.service'
 import { HostWindowService } from '../api/hostWindow'
 import { HostWindowService } from '../api/hostWindow'
@@ -16,9 +17,10 @@ export class WelcomeTabComponent extends BaseTabComponent {
     constructor (
     constructor (
         private hostWindow: HostWindowService,
         private hostWindow: HostWindowService,
         public config: ConfigService,
         public config: ConfigService,
+        translate: TranslateService,
     ) {
     ) {
         super()
         super()
-        this.setTitle('Welcome')
+        this.setTitle(translate.instant('Welcome'))
     }
     }
 
 
     async closeAndDisable () {
     async closeAndDisable () {

+ 1 - 0
tabby-core/src/configDefaults.yaml

@@ -38,3 +38,4 @@ enableExperimentalFeatures: false
 pluginBlacklist: []
 pluginBlacklist: []
 hacks:
 hacks:
   disableGPU: false
   disableGPU: false
+language: null

+ 47 - 45
tabby-core/src/hotkeys.ts

@@ -1,4 +1,5 @@
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
+import { TranslateService } from '@ngx-translate/core'
 import { ProfilesService } from './services/profiles.service'
 import { ProfilesService } from './services/profiles.service'
 import { HotkeyDescription, HotkeyProvider } from './api/hotkeyProvider'
 import { HotkeyDescription, HotkeyProvider } from './api/hotkeyProvider'
 import { PartialProfile, Profile } from './api'
 import { PartialProfile, Profile } from './api'
@@ -9,188 +10,189 @@ export class AppHotkeyProvider extends HotkeyProvider {
     hotkeys: HotkeyDescription[] = [
     hotkeys: HotkeyDescription[] = [
         {
         {
             id: 'profile-selector',
             id: 'profile-selector',
-            name: 'Show profile selector',
+            name: this.translate.instant('Show profile selector'),
         },
         },
         {
         {
             id: 'toggle-fullscreen',
             id: 'toggle-fullscreen',
-            name: 'Toggle fullscreen mode',
+            name: this.translate.instant('Toggle fullscreen mode'),
         },
         },
         {
         {
             id: 'rename-tab',
             id: 'rename-tab',
-            name: 'Rename Tab',
+            name: this.translate.instant('Rename Tab'),
         },
         },
         {
         {
             id: 'close-tab',
             id: 'close-tab',
-            name: 'Close tab',
+            name: this.translate.instant('Close tab'),
         },
         },
         {
         {
             id: 'reopen-tab',
             id: 'reopen-tab',
-            name: 'Reopen last tab',
+            name: this.translate.instant('Reopen last tab'),
         },
         },
         {
         {
             id: 'toggle-last-tab',
             id: 'toggle-last-tab',
-            name: 'Toggle last tab',
+            name: this.translate.instant('Toggle last tab'),
         },
         },
         {
         {
             id: 'next-tab',
             id: 'next-tab',
-            name: 'Next tab',
+            name: this.translate.instant('Next tab'),
         },
         },
         {
         {
             id: 'previous-tab',
             id: 'previous-tab',
-            name: 'Previous tab',
+            name: this.translate.instant('Previous tab'),
         },
         },
         {
         {
             id: 'move-tab-left',
             id: 'move-tab-left',
-            name: 'Move tab to the left',
+            name: this.translate.instant('Move tab to the left'),
         },
         },
         {
         {
             id: 'move-tab-right',
             id: 'move-tab-right',
-            name: 'Move tab to the right',
+            name: this.translate.instant('Move tab to the right'),
         },
         },
         {
         {
             id: 'rearrange-panes',
             id: 'rearrange-panes',
-            name: 'Show pane labels (for rearranging)',
+            name: this.translate.instant('Show pane labels (for rearranging)'),
         },
         },
         {
         {
             id: 'duplicate-tab',
             id: 'duplicate-tab',
-            name: 'Duplicate tab',
+            name: this.translate.instant('Duplicate tab'),
         },
         },
         {
         {
             id: 'tab-1',
             id: 'tab-1',
-            name: 'Tab 1',
+            name: this.translate.instant('Tab 1'),
         },
         },
         {
         {
             id: 'tab-2',
             id: 'tab-2',
-            name: 'Tab 2',
+            name: this.translate.instant('Tab 2'),
         },
         },
         {
         {
             id: 'tab-3',
             id: 'tab-3',
-            name: 'Tab 3',
+            name: this.translate.instant('Tab 3'),
         },
         },
         {
         {
             id: 'tab-4',
             id: 'tab-4',
-            name: 'Tab 4',
+            name: this.translate.instant('Tab 4'),
         },
         },
         {
         {
             id: 'tab-5',
             id: 'tab-5',
-            name: 'Tab 5',
+            name: this.translate.instant('Tab 5'),
         },
         },
         {
         {
             id: 'tab-6',
             id: 'tab-6',
-            name: 'Tab 6',
+            name: this.translate.instant('Tab 6'),
         },
         },
         {
         {
             id: 'tab-7',
             id: 'tab-7',
-            name: 'Tab 7',
+            name: this.translate.instant('Tab 7'),
         },
         },
         {
         {
             id: 'tab-8',
             id: 'tab-8',
-            name: 'Tab 8',
+            name: this.translate.instant('Tab 8'),
         },
         },
         {
         {
             id: 'tab-9',
             id: 'tab-9',
-            name: 'Tab 9',
+            name: this.translate.instant('Tab 9'),
         },
         },
         {
         {
             id: 'tab-10',
             id: 'tab-10',
-            name: 'Tab 10',
+            name: this.translate.instant('Tab 10'),
         },
         },
         {
         {
             id: 'tab-11',
             id: 'tab-11',
-            name: 'Tab 11',
+            name: this.translate.instant('Tab 11'),
         },
         },
         {
         {
             id: 'tab-12',
             id: 'tab-12',
-            name: 'Tab 12',
+            name: this.translate.instant('Tab 12'),
         },
         },
         {
         {
             id: 'tab-13',
             id: 'tab-13',
-            name: 'Tab 13',
+            name: this.translate.instant('Tab 13'),
         },
         },
         {
         {
             id: 'tab-14',
             id: 'tab-14',
-            name: 'Tab 14',
+            name: this.translate.instant('Tab 14'),
         },
         },
         {
         {
             id: 'tab-15',
             id: 'tab-15',
-            name: 'Tab 15',
+            name: this.translate.instant('Tab 15'),
         },
         },
         {
         {
             id: 'tab-16',
             id: 'tab-16',
-            name: 'Tab 16',
+            name: this.translate.instant('Tab 16'),
         },
         },
         {
         {
             id: 'tab-17',
             id: 'tab-17',
-            name: 'Tab 17',
+            name: this.translate.instant('Tab 17'),
         },
         },
         {
         {
             id: 'tab-18',
             id: 'tab-18',
-            name: 'Tab 18',
+            name: this.translate.instant('Tab 18'),
         },
         },
         {
         {
             id: 'tab-19',
             id: 'tab-19',
-            name: 'Tab 19',
+            name: this.translate.instant('Tab 19'),
         },
         },
         {
         {
             id: 'tab-20',
             id: 'tab-20',
-            name: 'Tab 20',
+            name: this.translate.instant('Tab 20'),
         },
         },
         {
         {
             id: 'split-right',
             id: 'split-right',
-            name: 'Split to the right',
+            name: this.translate.instant('Split to the right'),
         },
         },
         {
         {
             id: 'split-bottom',
             id: 'split-bottom',
-            name: 'Split to the bottom',
+            name: this.translate.instant('Split to the bottom'),
         },
         },
         {
         {
             id: 'split-left',
             id: 'split-left',
-            name: 'Split to the left',
+            name: this.translate.instant('Split to the left'),
         },
         },
         {
         {
             id: 'split-top',
             id: 'split-top',
-            name: 'Split to the top',
+            name: this.translate.instant('Split to the top'),
         },
         },
         {
         {
             id: 'pane-maximize',
             id: 'pane-maximize',
-            name: 'Maximize the active pane',
+            name: this.translate.instant('Maximize the active pane'),
         },
         },
         {
         {
             id: 'pane-nav-up',
             id: 'pane-nav-up',
-            name: 'Focus the pane above',
+            name: this.translate.instant('Focus the pane above'),
         },
         },
         {
         {
             id: 'pane-nav-down',
             id: 'pane-nav-down',
-            name: 'Focus the pane below',
+            name: this.translate.instant('Focus the pane below'),
         },
         },
         {
         {
             id: 'pane-nav-left',
             id: 'pane-nav-left',
-            name: 'Focus the pane on the left',
+            name: this.translate.instant('Focus the pane on the left'),
         },
         },
         {
         {
             id: 'pane-nav-right',
             id: 'pane-nav-right',
-            name: 'Focus the pane on the right',
+            name: this.translate.instant('Focus the pane on the right'),
         },
         },
         {
         {
             id: 'pane-nav-previous',
             id: 'pane-nav-previous',
-            name: 'Focus previous pane',
+            name: this.translate.instant('Focus previous pane'),
         },
         },
         {
         {
             id: 'pane-nav-next',
             id: 'pane-nav-next',
-            name: 'Focus next pane',
+            name: this.translate.instant('Focus next pane'),
         },
         },
         {
         {
             id: 'switch-profile',
             id: 'switch-profile',
-            name: 'Switch profile in the active pane',
+            name: this.translate.instant('Switch profile in the active pane'),
         },
         },
         {
         {
             id: 'close-pane',
             id: 'close-pane',
-            name: 'Close focused pane',
+            name: this.translate.instant('Close focused pane'),
         },
         },
     ]
     ]
 
 
     constructor (
     constructor (
         private profilesService: ProfilesService,
         private profilesService: ProfilesService,
+        private translate: TranslateService,
     ) { super() }
     ) { super() }
 
 
     async provide (): Promise<HotkeyDescription[]> {
     async provide (): Promise<HotkeyDescription[]> {

+ 39 - 5
tabby-core/src/index.ts

@@ -1,4 +1,4 @@
-import { NgModule, ModuleWithProviders } from '@angular/core'
+import { NgModule, ModuleWithProviders, LOCALE_ID } from '@angular/core'
 import { BrowserModule } from '@angular/platform-browser'
 import { BrowserModule } from '@angular/platform-browser'
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
 import { FormsModule } from '@angular/forms'
 import { FormsModule } from '@angular/forms'
@@ -7,6 +7,8 @@ import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-sc
 import { NgxFilesizeModule } from 'ngx-filesize'
 import { NgxFilesizeModule } from 'ngx-filesize'
 import { SortablejsModule } from 'ngx-sortablejs'
 import { SortablejsModule } from 'ngx-sortablejs'
 import { DragDropModule } from '@angular/cdk/drag-drop'
 import { DragDropModule } from '@angular/cdk/drag-drop'
+import { TranslateModule, TranslateCompiler, TranslateService } from '@ngx-translate/core'
+import { TranslateMessageFormatCompiler, MESSAGE_FORMAT_CONFIG } from 'ngx-translate-messageformat-compiler'
 
 
 import { AppRootComponent } from './components/appRoot.component'
 import { AppRootComponent } from './components/appRoot.component'
 import { CheckboxComponent } from './components/checkbox.component'
 import { CheckboxComponent } from './components/checkbox.component'
@@ -40,6 +42,7 @@ import { AppService } from './services/app.service'
 import { ConfigService } from './services/config.service'
 import { ConfigService } from './services/config.service'
 import { VaultFileProvider } from './services/vault.service'
 import { VaultFileProvider } from './services/vault.service'
 import { HotkeysService } from './services/hotkeys.service'
 import { HotkeysService } from './services/hotkeys.service'
+import { LocaleService, TranslateServiceWrapper } from './services/locale.service'
 
 
 import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
 import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
 import { CoreConfigProvider } from './config'
 import { CoreConfigProvider } from './config'
@@ -51,6 +54,10 @@ import { SplitLayoutProfilesService } from './profiles'
 
 
 import 'perfect-scrollbar/css/perfect-scrollbar.css'
 import 'perfect-scrollbar/css/perfect-scrollbar.css'
 
 
+export function TranslateMessageFormatCompilerFactory (): TranslateMessageFormatCompiler {
+    return new TranslateMessageFormatCompiler()
+}
+
 const PROVIDERS = [
 const PROVIDERS = [
     { provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
     { provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
     { provide: Theme, useClass: StandardTheme, multi: true },
     { provide: Theme, useClass: StandardTheme, multi: true },
@@ -68,6 +75,19 @@ const PROVIDERS = [
     { provide: FileProvider, useClass: VaultFileProvider, multi: true },
     { provide: FileProvider, useClass: VaultFileProvider, multi: true },
     { provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
     { provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
     { provide: ProfileProvider, useExisting: SplitLayoutProfilesService, multi: true },
     { provide: ProfileProvider, useExisting: SplitLayoutProfilesService, multi: true },
+    {
+        provide: LOCALE_ID,
+        deps: [LocaleService],
+        useFactory: locale => locale.getLocale(),
+    },
+    {
+        provide: MESSAGE_FORMAT_CONFIG,
+        useValue: LocaleService.allLocales,
+    },
+    {
+        provide: TranslateService,
+        useClass: TranslateServiceWrapper,
+    },
 ]
 ]
 
 
 /** @hidden */
 /** @hidden */
@@ -81,6 +101,7 @@ const PROVIDERS = [
         PerfectScrollbarModule,
         PerfectScrollbarModule,
         DragDropModule,
         DragDropModule,
         SortablejsModule.forRoot({ animation: 150 }),
         SortablejsModule.forRoot({ animation: 150 }),
+        TranslateModule,
     ],
     ],
     declarations: [
     declarations: [
         AppRootComponent,
         AppRootComponent,
@@ -127,6 +148,7 @@ const PROVIDERS = [
         AlwaysVisibleTypeaheadDirective,
         AlwaysVisibleTypeaheadDirective,
         SortablejsModule,
         SortablejsModule,
         DragDropModule,
         DragDropModule,
+        TranslateModule,
     ],
     ],
 })
 })
 export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
 export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
@@ -135,6 +157,8 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex
         config: ConfigService,
         config: ConfigService,
         platform: PlatformService,
         platform: PlatformService,
         hotkeys: HotkeysService,
         hotkeys: HotkeysService,
+        public locale: LocaleService,
+        private translate: TranslateService,
         private profilesService: ProfilesService,
         private profilesService: ProfilesService,
         private selector: SelectorService,
         private selector: SelectorService,
     ) {
     ) {
@@ -182,8 +206,8 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex
 
 
         if (provider.supportsQuickConnect) {
         if (provider.supportsQuickConnect) {
             options.push({
             options.push({
-                name: 'Quick connect',
-                freeInputPattern: 'Connect to "%s"...',
+                name: this.translate.instant('Quick connect'),
+                freeInputPattern: this.translate.instant('Connect to "%s"...'),
                 icon: 'fas fa-arrow-right',
                 icon: 'fas fa-arrow-right',
                 callback: query => {
                 callback: query => {
                     const p = provider.quickConnect(query)
                     const p = provider.quickConnect(query)
@@ -194,13 +218,23 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex
             })
             })
         }
         }
 
 
-        await this.selector.show('Select profile', options)
+        await this.selector.show(this.translate.instant('Select profile'), options)
     }
     }
 
 
     static forRoot (): ModuleWithProviders<AppModule> {
     static forRoot (): ModuleWithProviders<AppModule> {
+        const translateModule = TranslateModule.forRoot({
+            defaultLanguage: 'en',
+            compiler: {
+                provide: TranslateCompiler,
+                useFactory: TranslateMessageFormatCompilerFactory,
+            },
+        })
         return {
         return {
             ngModule: AppModule,
             ngModule: AppModule,
-            providers: PROVIDERS,
+            providers: [
+                ...PROVIDERS,
+                ...translateModule.providers!.filter(x => x !== TranslateService),
+            ],
         }
         }
     }
     }
 }
 }

+ 3 - 1
tabby-core/src/profiles.ts

@@ -1,6 +1,7 @@
 import slugify from 'slugify'
 import slugify from 'slugify'
 import { v4 as uuidv4 } from 'uuid'
 import { v4 as uuidv4 } from 'uuid'
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
+import { TranslateService } from '@ngx-translate/core'
 import { ConfigService, NewTabParameters, PartialProfile, Profile, ProfileProvider } from './api'
 import { ConfigService, NewTabParameters, PartialProfile, Profile, ProfileProvider } from './api'
 import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
 import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
 
 
@@ -15,7 +16,7 @@ export interface SplitLayoutProfile extends Profile {
 @Injectable({ providedIn: 'root' })
 @Injectable({ providedIn: 'root' })
 export class SplitLayoutProfilesService extends ProfileProvider<SplitLayoutProfile> {
 export class SplitLayoutProfilesService extends ProfileProvider<SplitLayoutProfile> {
     id = 'split-layout'
     id = 'split-layout'
-    name = 'Saved layout'
+    name = this.translate.instant('Saved layout')
     configDefaults = {
     configDefaults = {
         options: {
         options: {
             recoveryToken: null,
             recoveryToken: null,
@@ -25,6 +26,7 @@ export class SplitLayoutProfilesService extends ProfileProvider<SplitLayoutProfi
     constructor (
     constructor (
         private splitTabRecoveryProvider: SplitTabRecoveryProvider,
         private splitTabRecoveryProvider: SplitTabRecoveryProvider,
         private config: ConfigService,
         private config: ConfigService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
     }
     }

+ 13 - 4
tabby-core/src/services/config.service.ts

@@ -4,6 +4,7 @@ import { v4 as uuidv4 } from 'uuid'
 import * as yaml from 'js-yaml'
 import * as yaml from 'js-yaml'
 import { Observable, Subject, AsyncSubject } from 'rxjs'
 import { Observable, Subject, AsyncSubject } from 'rxjs'
 import { Injectable, Inject } from '@angular/core'
 import { Injectable, Inject } from '@angular/core'
+import { TranslateService } from '@ngx-translate/core'
 import { ConfigProvider } from '../api/configProvider'
 import { ConfigProvider } from '../api/configProvider'
 import { PlatformService } from '../api/platform'
 import { PlatformService } from '../api/platform'
 import { HostAppService } from '../api/hostApp'
 import { HostAppService } from '../api/hostApp'
@@ -136,6 +137,7 @@ export class ConfigService {
         private hostApp: HostAppService,
         private hostApp: HostAppService,
         private platform: PlatformService,
         private platform: PlatformService,
         private vault: VaultService,
         private vault: VaultService,
+        private translate: TranslateService,
         @Inject(ConfigProvider) private configProviders: ConfigProvider[],
         @Inject(ConfigProvider) private configProviders: ConfigProvider[],
     ) {
     ) {
         this.defaults = this.mergeDefaults()
         this.defaults = this.mergeDefaults()
@@ -360,9 +362,13 @@ export class ConfigService {
             } catch (e) {
             } catch (e) {
                 let result = await this.platform.showMessageBox({
                 let result = await this.platform.showMessageBox({
                     type: 'error',
                     type: 'error',
-                    message: 'Could not decrypt config',
+                    message: this.translate.instant('Could not decrypt config'),
                     detail: e.toString(),
                     detail: e.toString(),
-                    buttons: ['Try again', 'Erase config', 'Quit'],
+                    buttons: [
+                        this.translate.instant('Try again'),
+                        this.translate.instant('Erase config'),
+                        this.translate.instant('Quit'),
+                    ],
                     defaultId: 0,
                     defaultId: 0,
                 })
                 })
                 if (result.response === 2) {
                 if (result.response === 2) {
@@ -371,9 +377,12 @@ export class ConfigService {
                 if (result.response === 1) {
                 if (result.response === 1) {
                     result = await this.platform.showMessageBox({
                     result = await this.platform.showMessageBox({
                         type: 'warning',
                         type: 'warning',
-                        message: 'Are you sure?',
+                        message: this.translate.instant('Are you sure?'),
                         detail: e.toString(),
                         detail: e.toString(),
-                        buttons: ['Erase config', 'Quit'],
+                        buttons: [
+                            this.translate.instant('Erase config'),
+                            this.translate.instant('Quit'),
+                        ],
                         defaultId: 1,
                         defaultId: 1,
                         cancelId: 1,
                         cancelId: 1,
                     })
                     })

+ 10 - 5
tabby-core/src/services/fileProviders.service.ts

@@ -1,4 +1,5 @@
 import { Inject, Injectable } from '@angular/core'
 import { Inject, Injectable } from '@angular/core'
+import { TranslateService } from '@ngx-translate/core'
 import { FileProvider, NotificationsService, SelectorService } from '../api'
 import { FileProvider, NotificationsService, SelectorService } from '../api'
 
 
 @Injectable({ providedIn: 'root' })
 @Injectable({ providedIn: 'root' })
@@ -7,6 +8,7 @@ export class FileProvidersService {
     private constructor (
     private constructor (
         private selector: SelectorService,
         private selector: SelectorService,
         private notifications: NotificationsService,
         private notifications: NotificationsService,
+        private translate: TranslateService,
         @Inject(FileProvider) private fileProviders: FileProvider[],
         @Inject(FileProvider) private fileProviders: FileProvider[],
     ) { }
     ) { }
 
 
@@ -34,15 +36,18 @@ export class FileProvidersService {
             }
             }
         }))
         }))
         if (!providers.length) {
         if (!providers.length) {
-            this.notifications.error('Vault master passphrase needs to be set to allow storing secrets')
+            this.notifications.error(this.translate.instant('Vault master passphrase needs to be set to allow storing secrets'))
             throw new Error('No available file providers')
             throw new Error('No available file providers')
         }
         }
         if (providers.length === 1) {
         if (providers.length === 1) {
             return providers[0]
             return providers[0]
         }
         }
-        return this.selector.show('Select file storage', providers.map(p => ({
-            name: p.name,
-            result: p,
-        })))
+        return this.selector.show(
+            this.translate.instant('Select file storage'),
+            providers.map(p => ({
+                name: p.name,
+                result: p,
+            }))
+        )
     }
     }
 }
 }

+ 134 - 0
tabby-core/src/services/locale.service.ts

@@ -0,0 +1,134 @@
+import { Injectable } from '@angular/core'
+import { registerLocaleData } from '@angular/common'
+import { TranslateService } from '@ngx-translate/core'
+
+import localeEN from '@angular/common/locales/en-GB'
+import localeRU from '@angular/common/locales/ru'
+import { Observable, Subject } from 'rxjs'
+import { distinctUntilChanged } from 'rxjs/operators'
+import { ConfigService } from './config.service'
+import { LogService, Logger } from './log.service'
+
+registerLocaleData(localeEN)
+registerLocaleData(localeRU)
+
+@Injectable({ providedIn: 'root' })
+export class TranslateServiceWrapper extends TranslateService {
+    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+    getParsedResult (translations: any, key: any, interpolateParams?: any): any {
+        this.translations[this.defaultLang][key] ??= this.compiler.compile(key, this.defaultLang)
+        return super.getParsedResult(translations, key, interpolateParams)
+    }
+}
+
+@Injectable({ providedIn: 'root' })
+export class LocaleService {
+    private logger: Logger
+
+    static readonly allLocales = ['en', 'de', 'fr', 'ru']
+
+    get localeChanged$ (): Observable<string> {
+        return this.localeChanged.pipe(distinctUntilChanged())
+    }
+
+    get catalogChanged$ (): Observable<Record<string, string | undefined>> {
+        return this.catalogChanged.pipe(distinctUntilChanged())
+    }
+
+    readonly allLanguages: { code: string, name: string }[]
+    private translations = {
+        en: {
+            Close: 'Close',
+        },
+        ru: {
+            Close: 'Закрыть',
+        },
+    }
+    private locale = 'en'
+    private localeChanged = new Subject<string>()
+    private catalogChanged = new Subject<Record<string, string | undefined>>()
+
+    constructor (
+        private config: ConfigService,
+        private translate: TranslateService,
+        log: LogService,
+    ) {
+        this.logger = log.create('translate')
+        config.changed$.subscribe(() => {
+            this.refresh()
+        })
+        config.ready$.subscribe(() => {
+            this.refresh()
+        })
+
+        this.allLanguages = [
+            {
+                code: 'en',
+                name: translate.instant('English'),
+            },
+            {
+                code: 'de',
+                name: translate.instant('German'),
+            },
+            {
+                code: 'fr',
+                name: translate.instant('French'),
+            },
+            /* {
+                code: 'it',
+                name: translate.instant('Italian'),
+            },
+            {
+                code: 'es',
+                name: translate.instant('Spanish'),
+            }, */
+            {
+                code: 'ru',
+                name: translate.instant('Russian'),
+            },
+            /* {
+                code: 'ar',
+                name: translate.instant('Arabic'),
+            }, */
+        ]
+    }
+
+    refresh (): void {
+        let lang = this.config.store.language
+        if (!lang) {
+            const systemLanguage = navigator.language.toLowerCase().split('-')[0]
+            if (this.allLanguages.some(x => x.code === systemLanguage)) {
+                lang = systemLanguage
+            }
+        }
+        lang ??= 'en'
+        this.setLocale(lang)
+    }
+
+    async setLocale (lang: string): Promise<void> {
+        const strings = this.translations[lang]
+
+        if (!this.translate.langs.includes(lang)) {
+            this.translate.addLangs([lang])
+
+            const po = require(`../../../locale/${lang}.po`).translations['']
+            const translation = {}
+            for (const k of Object.keys(po)) {
+                translation[k] = po[k].msgstr[0] || k
+            }
+
+            this.translate.setTranslation(lang, translation)
+        }
+
+        this.translate.setDefaultLang(lang)
+
+        this.locale = lang
+        this.localeChanged.next(lang)
+        this.logger.debug('Setting language to', lang)
+        this.catalogChanged.next(strings)
+    }
+
+    getLocale (): string {
+        return this.locale
+    }
+}

+ 9 - 7
tabby-core/src/services/profiles.service.ts

@@ -1,4 +1,5 @@
 import { Injectable, Inject } from '@angular/core'
 import { Injectable, Inject } from '@angular/core'
+import { TranslateService } from '@ngx-translate/core'
 import { NewTabParameters } from './tabs.service'
 import { NewTabParameters } from './tabs.service'
 import { BaseTabComponent } from '../components/baseTab.component'
 import { BaseTabComponent } from '../components/baseTab.component'
 import { PartialProfile, Profile, ProfileProvider } from '../api/profileProvider'
 import { PartialProfile, Profile, ProfileProvider } from '../api/profileProvider'
@@ -29,6 +30,7 @@ export class ProfilesService {
         private config: ConfigService,
         private config: ConfigService,
         private notifications: NotificationsService,
         private notifications: NotificationsService,
         private selector: SelectorService,
         private selector: SelectorService,
+        private translate: TranslateService,
         @Inject(ProfileProvider) private profileProviders: ProfileProvider<Profile>[],
         @Inject(ProfileProvider) private profileProviders: ProfileProvider<Profile>[],
     ) { }
     ) { }
 
 
@@ -103,7 +105,7 @@ export class ProfilesService {
 
 
                 let options: SelectorOption<void>[] = recentProfiles.map(p => ({
                 let options: SelectorOption<void>[] = recentProfiles.map(p => ({
                     ...this.selectorOptionForProfile(p),
                     ...this.selectorOptionForProfile(p),
-                    group: 'Recent',
+                    group: this.translate.instant('Recent'),
                     icon: 'fas fa-history',
                     icon: 'fas fa-history',
                     color: p.color,
                     color: p.color,
                     callback: async () => {
                     callback: async () => {
@@ -115,8 +117,8 @@ export class ProfilesService {
                 }))
                 }))
                 if (recentProfiles.length) {
                 if (recentProfiles.length) {
                     options.push({
                     options.push({
-                        name: 'Clear recent profiles',
-                        group: 'Recent',
+                        name: this.translate.instant('Clear recent profiles'),
+                        group: this.translate.instant('Recent'),
                         icon: 'fas fa-eraser',
                         icon: 'fas fa-eraser',
                         callback: async () => {
                         callback: async () => {
                             window.localStorage.removeItem('recentProfiles')
                             window.localStorage.removeItem('recentProfiles')
@@ -142,7 +144,7 @@ export class ProfilesService {
                 try {
                 try {
                     const { SettingsTabComponent } = window['nodeRequire']('tabby-settings')
                     const { SettingsTabComponent } = window['nodeRequire']('tabby-settings')
                     options.push({
                     options.push({
-                        name: 'Manage profiles',
+                        name: this.translate.instant('Manage profiles'),
                         icon: 'fas fa-window-restore',
                         icon: 'fas fa-window-restore',
                         callback: () => {
                         callback: () => {
                             this.app.openNewTabRaw({
                             this.app.openNewTabRaw({
@@ -156,8 +158,8 @@ export class ProfilesService {
 
 
                 if (this.getProviders().some(x => x.supportsQuickConnect)) {
                 if (this.getProviders().some(x => x.supportsQuickConnect)) {
                     options.push({
                     options.push({
-                        name: 'Quick connect',
-                        freeInputPattern: 'Connect to "%s"...',
+                        name: this.translate.instant('Quick connect'),
+                        freeInputPattern: this.translate.instant('Connect to "%s"...'),
                         icon: 'fas fa-arrow-right',
                         icon: 'fas fa-arrow-right',
                         callback: query => {
                         callback: query => {
                             const profile = this.quickConnect(query)
                             const profile = this.quickConnect(query)
@@ -165,7 +167,7 @@ export class ProfilesService {
                         },
                         },
                     })
                     })
                 }
                 }
-                await this.selector.show('Select profile or enter an address', options)
+                await this.selector.show(this.translate.instant('Select profile or enter an address'), options)
             } catch (err) {
             } catch (err) {
                 reject(err)
                 reject(err)
             }
             }

+ 26 - 21
tabby-core/src/tabContextMenu.ts

@@ -1,6 +1,7 @@
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
+import { TranslateService } from '@ngx-translate/core'
 import { Subscription } from 'rxjs'
 import { Subscription } from 'rxjs'
 import { AppService } from './services/app.service'
 import { AppService } from './services/app.service'
 import { BaseTabComponent } from './components/baseTab.component'
 import { BaseTabComponent } from './components/baseTab.component'
@@ -22,6 +23,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
 
 
     constructor (
     constructor (
         private app: AppService,
         private app: AppService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
     }
     }
@@ -29,7 +31,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
     async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
     async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
         let items: MenuItemOptions[] = [
         let items: MenuItemOptions[] = [
             {
             {
-                label: 'Close',
+                label: this.translate.instant('Close'),
                 click: () => {
                 click: () => {
                     if (this.app.tabs.includes(tab)) {
                     if (this.app.tabs.includes(tab)) {
                         this.app.closeTab(tab, true)
                         this.app.closeTab(tab, true)
@@ -43,7 +45,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
             items = [
             items = [
                 ...items,
                 ...items,
                 {
                 {
-                    label: 'Close other tabs',
+                    label: this.translate.instant('Close other tabs'),
                     click: () => {
                     click: () => {
                         for (const t of this.app.tabs.filter(x => x !== tab)) {
                         for (const t of this.app.tabs.filter(x => x !== tab)) {
                             this.app.closeTab(t, true)
                             this.app.closeTab(t, true)
@@ -51,7 +53,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
                     },
                     },
                 },
                 },
                 {
                 {
-                    label: 'Close tabs to the right',
+                    label: this.translate.instant('Close tabs to the right'),
                     click: () => {
                     click: () => {
                         for (const t of this.app.tabs.slice(this.app.tabs.indexOf(tab) + 1)) {
                         for (const t of this.app.tabs.slice(this.app.tabs.indexOf(tab) + 1)) {
                             this.app.closeTab(t, true)
                             this.app.closeTab(t, true)
@@ -59,7 +61,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
                     },
                     },
                 },
                 },
                 {
                 {
-                    label: 'Close tabs to the left',
+                    label: this.translate.instant('Close tabs to the left'),
                     click: () => {
                     click: () => {
                         for (const t of this.app.tabs.slice(0, this.app.tabs.indexOf(tab))) {
                         for (const t of this.app.tabs.slice(0, this.app.tabs.indexOf(tab))) {
                             this.app.closeTab(t, true)
                             this.app.closeTab(t, true)
@@ -71,13 +73,13 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
             if (tab.parent instanceof SplitTabComponent) {
             if (tab.parent instanceof SplitTabComponent) {
                 const directions: SplitDirection[] = ['r', 'b', 'l', 't']
                 const directions: SplitDirection[] = ['r', 'b', 'l', 't']
                 items.push({
                 items.push({
-                    label: 'Split',
+                    label: this.translate.instant('Split'),
                     submenu: directions.map(dir => ({
                     submenu: directions.map(dir => ({
                         label: {
                         label: {
-                            r: 'Right',
-                            b: 'Down',
-                            l: 'Left',
-                            t: 'Up',
+                            r: this.translate.instant('Right'),
+                            b: this.translate.instant('Down'),
+                            l: this.translate.instant('Left'),
+                            t: this.translate.instant('Up'),
                         }[dir],
                         }[dir],
                         click: () => {
                         click: () => {
                             (tab.parent as SplitTabComponent).splitTab(tab, dir)
                             (tab.parent as SplitTabComponent).splitTab(tab, dir)
@@ -99,6 +101,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
         private app: AppService,
         private app: AppService,
         private ngbModal: NgbModal,
         private ngbModal: NgbModal,
         private splitLayoutProfilesService: SplitLayoutProfilesService,
         private splitLayoutProfilesService: SplitLayoutProfilesService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
     }
     }
@@ -109,18 +112,18 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
             items = [
             items = [
                 ...items,
                 ...items,
                 {
                 {
-                    label: 'Rename',
+                    label: this.translate.instant('Rename'),
                     click: () => tabHeader.showRenameTabModal(),
                     click: () => tabHeader.showRenameTabModal(),
                 },
                 },
                 {
                 {
-                    label: 'Duplicate',
+                    label: this.translate.instant('Duplicate'),
                     click: () => this.app.duplicateTab(tab),
                     click: () => this.app.duplicateTab(tab),
                 },
                 },
                 {
                 {
-                    label: 'Color',
+                    label: this.translate.instant('Color'),
                     sublabel: TAB_COLORS.find(x => x.value === tab.color)?.name,
                     sublabel: TAB_COLORS.find(x => x.value === tab.color)?.name,
                     submenu: TAB_COLORS.map(color => ({
                     submenu: TAB_COLORS.map(color => ({
-                        label: color.name,
+                        label: this.translate.instant(color.name),
                         type: 'radio',
                         type: 'radio',
                         checked: tab.color === color.value,
                         checked: tab.color === color.value,
                         click: () => {
                         click: () => {
@@ -132,10 +135,10 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
 
 
             if (tab instanceof SplitTabComponent && tab.getAllTabs().length > 1) {
             if (tab instanceof SplitTabComponent && tab.getAllTabs().length > 1) {
                 items.push({
                 items.push({
-                    label: 'Save layout as profile',
+                    label: this.translate.instant('Save layout as profile'),
                     click: async () => {
                     click: async () => {
                         const modal = this.ngbModal.open(PromptModalComponent)
                         const modal = this.ngbModal.open(PromptModalComponent)
-                        modal.componentInstance.prompt = 'Profile name'
+                        modal.componentInstance.prompt = this.translate.instant('Profile name')
                         const name = (await modal.result)?.value
                         const name = (await modal.result)?.value
                         if (!name) {
                         if (!name) {
                             return
                             return
@@ -154,6 +157,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
 export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
 export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
     constructor (
     constructor (
         private app: AppService,
         private app: AppService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
     }
     }
@@ -167,10 +171,10 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
         if (process) {
         if (process) {
             items.push({
             items.push({
                 enabled: false,
                 enabled: false,
-                label: 'Current process: ' + process.name,
+                label: this.translate.instant('Current process: {name}', process),
             })
             })
             items.push({
             items.push({
-                label: 'Notify when done',
+                label: this.translate.instant('Notify when done'),
                 type: 'checkbox',
                 type: 'checkbox',
                 checked: extTab.__completionNotificationEnabled,
                 checked: extTab.__completionNotificationEnabled,
                 click: () => {
                 click: () => {
@@ -178,7 +182,7 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
 
 
                     if (extTab.__completionNotificationEnabled) {
                     if (extTab.__completionNotificationEnabled) {
                         this.app.observeTabCompletion(tab).subscribe(() => {
                         this.app.observeTabCompletion(tab).subscribe(() => {
-                            new Notification('Process completed', {
+                            new Notification(this.translate.instant('Process completed'), {
                                 body: process.name,
                                 body: process.name,
                             }).addEventListener('click', () => {
                             }).addEventListener('click', () => {
                                 this.app.selectTab(tab)
                                 this.app.selectTab(tab)
@@ -192,7 +196,7 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
             })
             })
         }
         }
         items.push({
         items.push({
-            label: 'Notify on activity',
+            label: this.translate.instant('Notify on activity'),
             type: 'checkbox',
             type: 'checkbox',
             checked: !!extTab.__outputNotificationSubscription,
             checked: !!extTab.__outputNotificationSubscription,
             click: () => {
             click: () => {
@@ -204,7 +208,7 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
                         if (extTab.__outputNotificationSubscription && active) {
                         if (extTab.__outputNotificationSubscription && active) {
                             extTab.__outputNotificationSubscription.unsubscribe()
                             extTab.__outputNotificationSubscription.unsubscribe()
                             extTab.__outputNotificationSubscription = null
                             extTab.__outputNotificationSubscription = null
-                            new Notification('Tab activity', {
+                            new Notification(this.translate.instant('Tab activity'), {
                                 body: tab.title,
                                 body: tab.title,
                             }).addEventListener('click', () => {
                             }).addEventListener('click', () => {
                                 this.app.selectTab(tab)
                                 this.app.selectTab(tab)
@@ -228,6 +232,7 @@ export class ProfilesContextMenu extends TabContextMenuItemProvider {
         private profilesService: ProfilesService,
         private profilesService: ProfilesService,
         private tabsService: TabsService,
         private tabsService: TabsService,
         private app: AppService,
         private app: AppService,
+        private translate: TranslateService,
         hotkeys: HotkeysService,
         hotkeys: HotkeysService,
     ) {
     ) {
         super()
         super()
@@ -270,7 +275,7 @@ export class ProfilesContextMenu extends TabContextMenuItemProvider {
         if (!tabHeader && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
         if (!tabHeader && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
             return [
             return [
                 {
                 {
-                    label: 'Switch profile',
+                    label: this.translate.instant('Switch profile'),
                     click: () => this.switchTabProfile(tab),
                     click: () => this.switchTabProfile(tab),
                 },
                 },
             ]
             ]

+ 16 - 3
tabby-core/src/theme.ts

@@ -1,28 +1,41 @@
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
+import { TranslateService } from '@ngx-translate/core'
 import { Theme } from './api'
 import { Theme } from './api'
 
 
 /** @hidden */
 /** @hidden */
 @Injectable()
 @Injectable()
 export class StandardTheme extends Theme {
 export class StandardTheme extends Theme {
-    name = 'Standard'
+    name = this.translate.instant('Standard')
     css = require('./theme.scss')
     css = require('./theme.scss')
     terminalBackground = '#222a33'
     terminalBackground = '#222a33'
+
+    constructor (private translate: TranslateService) {
+        super()
+    }
 }
 }
 
 
 /** @hidden */
 /** @hidden */
 @Injectable()
 @Injectable()
 export class StandardCompactTheme extends Theme {
 export class StandardCompactTheme extends Theme {
-    name = 'Compact'
+    name = this.translate.instant('Compact')
     css = require('./theme.compact.scss')
     css = require('./theme.compact.scss')
     terminalBackground = '#222a33'
     terminalBackground = '#222a33'
     macOSWindowButtonsInsetX = 8
     macOSWindowButtonsInsetX = 8
     macOSWindowButtonsInsetY = 6
     macOSWindowButtonsInsetY = 6
+
+    constructor (private translate: TranslateService) {
+        super()
+    }
 }
 }
 
 
 /** @hidden */
 /** @hidden */
 @Injectable()
 @Injectable()
 export class PaperTheme extends Theme {
 export class PaperTheme extends Theme {
-    name = 'Paper'
+    name = this.translate.instant('Paper')
     css = require('./theme.paper.scss')
     css = require('./theme.paper.scss')
     terminalBackground = '#f7f1e0'
     terminalBackground = '#f7f1e0'
+
+    constructor (private translate: TranslateService) {
+        super()
+    }
 }
 }

+ 8 - 7
tabby-core/src/utils.ts

@@ -1,5 +1,6 @@
 import * as os from 'os'
 import * as os from 'os'
 import { NgZone } from '@angular/core'
 import { NgZone } from '@angular/core'
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
 
 
 export const WIN_BUILD_CONPTY_SUPPORTED = 17692
 export const WIN_BUILD_CONPTY_SUPPORTED = 17692
 export const WIN_BUILD_CONPTY_STABLE = 18309
 export const WIN_BUILD_CONPTY_STABLE = 18309
@@ -56,13 +57,13 @@ export class ResettableTimeout {
 }
 }
 
 
 export const TAB_COLORS = [
 export const TAB_COLORS = [
-    { name: 'No color', value: null },
-    { name: 'Blue', value: '#0275d8' },
-    { name: 'Green', value: '#5cb85c' },
-    { name: 'Orange', value: '#f0ad4e' },
-    { name: 'Purple', value: '#613d7c' },
-    { name: 'Red', value: '#d9534f' },
-    { name: 'Yellow', value: '#ffd500' },
+    { name: _('No color'), value: null },
+    { name: _('Blue'), value: '#0275d8' },
+    { name: _('Green'), value: '#5cb85c' },
+    { name: _('Orange'), value: '#f0ad4e' },
+    { name: _('Purple'), value: '#613d7c' },
+    { name: _('Red'), value: '#d9534f' },
+    { name: _('Yellow'), value: '#ffd500' },
 ]
 ]
 
 
 export function serializeFunction <T extends () => Promise<any>> (fn: T): T {
 export function serializeFunction <T extends () => Promise<any>> (fn: T): T {

+ 55 - 0
tabby-core/yarn.lock

@@ -2,6 +2,13 @@
 # yarn lockfile v1
 # yarn lockfile v1
 
 
 
 
+"@ngx-translate/core@^14.0.0":
+  version "14.0.0"
+  resolved "https://registry.yarnpkg.com/@ngx-translate/core/-/core-14.0.0.tgz#af421d0e1a28376843f0fed375cd2fae7630a5ff"
+  integrity sha512-UevdwNCXMRCdJv//0kC8h2eSfmi02r29xeE8E9gJ1Al4D4jEJ7eiLPdjslTMc21oJNGguqqWeEVjf64SFtvw2w==
+  dependencies:
+    tslib "^2.3.0"
+
 agent-base@6:
 agent-base@6:
   version "6.0.2"
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
@@ -56,6 +63,37 @@ js-yaml@^4.0.0:
   dependencies:
   dependencies:
     argparse "^2.0.1"
     argparse "^2.0.1"
 
 
+make-plural@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-4.3.0.tgz#f23de08efdb0cac2e0c9ba9f315b0dff6b4c2735"
+  integrity sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==
+  optionalDependencies:
+    minimist "^1.2.0"
+
+messageformat-formatters@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz#0492c1402a48775f751c9b17c0354e92be012b08"
+  integrity sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg==
+
+messageformat-parser@^4.1.2:
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/messageformat-parser/-/messageformat-parser-4.1.3.tgz#b824787f57fcda7d50769f5b63e8d4fda68f5b9e"
+  integrity sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg==
+
+messageformat@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/messageformat/-/messageformat-2.3.0.tgz#de263c49029d5eae65d7ee25e0754f57f425ad91"
+  integrity sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==
+  dependencies:
+    make-plural "^4.3.0"
+    messageformat-formatters "^2.0.1"
+    messageformat-parser "^4.1.2"
+
+minimist@^1.2.0:
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+  integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+
 mixpanel@^0.13.0:
 mixpanel@^0.13.0:
   version "0.13.0"
   version "0.13.0"
   resolved "https://registry.yarnpkg.com/mixpanel/-/mixpanel-0.13.0.tgz#699bf510d9ba013c75edcf979ff1e24085fde9d2"
   resolved "https://registry.yarnpkg.com/mixpanel/-/mixpanel-0.13.0.tgz#699bf510d9ba013c75edcf979ff1e24085fde9d2"
@@ -85,6 +123,13 @@ ngx-perfect-scrollbar@^10.1.0:
     resize-observer-polyfill "^1.5.0"
     resize-observer-polyfill "^1.5.0"
     tslib "^2.0.0"
     tslib "^2.0.0"
 
 
+ngx-translate-messageformat-compiler@^4.11.0:
+  version "4.11.0"
+  resolved "https://registry.yarnpkg.com/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.11.0.tgz#c9b71dd139ba5fcdcd809001e22622de589fd707"
+  integrity sha512-OdGfWV4fF3DhZqGIHcLmOnQDufugmZ+E90NYr1UPGRZgT10lilr9oLmIrisy3lW4THnZFNo9JXsX7+fX84LbDw==
+  dependencies:
+    tslib "^1.10.0"
+
 [email protected]:
 [email protected]:
   version "1.5.0"
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.5.0.tgz#821d224ed8ff61990c23f26db63048cdc75b6b83"
   resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.5.0.tgz#821d224ed8ff61990c23f26db63048cdc75b6b83"
@@ -116,11 +161,21 @@ string_decoder@^1.1.1:
   dependencies:
   dependencies:
     safe-buffer "~5.2.0"
     safe-buffer "~5.2.0"
 
 
+tslib@^1.10.0:
+  version "1.14.1"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
+  integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+
 tslib@^2.0.0:
 tslib@^2.0.0:
   version "2.1.0"
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
   integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
   integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
 
 
+tslib@^2.3.0:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
+  integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
+
 util-deprecate@^1.0.1:
 util-deprecate@^1.0.1:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"

+ 5 - 3
tabby-electron/src/hotkeys.ts

@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
-import { HotkeyDescription, HotkeyProvider } from 'tabby-core'
+import { HotkeyDescription, HotkeyProvider, TranslateService } from 'tabby-core'
 
 
 /** @hidden */
 /** @hidden */
 @Injectable()
 @Injectable()
@@ -7,14 +7,16 @@ export class ElectronHotkeyProvider extends HotkeyProvider {
     hotkeys: HotkeyDescription[] = [
     hotkeys: HotkeyDescription[] = [
         {
         {
             id: 'new-window',
             id: 'new-window',
-            name: 'New window',
+            name: this.translate.instant('New window'),
         },
         },
         {
         {
             id: 'toggle-window',
             id: 'toggle-window',
-            name: 'Toggle terminal window',
+            name: this.translate.instant('Toggle terminal window'),
         },
         },
     ]
     ]
 
 
+    constructor (private translate: TranslateService) { super() }
+
     async provide (): Promise<HotkeyDescription[]> {
     async provide (): Promise<HotkeyDescription[]> {
         return this.hotkeys
         return this.hotkeys
     }
     }

+ 3 - 2
tabby-electron/src/services/platform.service.ts

@@ -7,7 +7,7 @@ import { promisify } from 'util'
 import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
 import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
 import { execFile } from 'mz/child_process'
 import { execFile } from 'mz/child_process'
 import { Injectable, NgZone } from '@angular/core'
 import { Injectable, NgZone } from '@angular/core'
-import { PlatformService, ClipboardContent, HostAppService, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise } from 'tabby-core'
+import { PlatformService, ClipboardContent, HostAppService, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise, TranslateService } from 'tabby-core'
 import { ElectronService } from '../services/electron.service'
 import { ElectronService } from '../services/electron.service'
 import { ElectronHostWindow } from './hostWindow.service'
 import { ElectronHostWindow } from './hostWindow.service'
 import { ShellIntegrationService } from './shellIntegration.service'
 import { ShellIntegrationService } from './shellIntegration.service'
@@ -34,6 +34,7 @@ export class ElectronPlatformService extends PlatformService {
         private electron: ElectronService,
         private electron: ElectronService,
         private zone: NgZone,
         private zone: NgZone,
         private shellIntegration: ShellIntegrationService,
         private shellIntegration: ShellIntegrationService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
         this.configPath = path.join(electron.app.getPath('userData'), 'config.yaml')
         this.configPath = path.join(electron.app.getPath('userData'), 'config.yaml')
@@ -204,7 +205,7 @@ export class ElectronPlatformService extends PlatformService {
             const result = await this.electron.dialog.showOpenDialog(
             const result = await this.electron.dialog.showOpenDialog(
                 this.hostWindow.getWindow(),
                 this.hostWindow.getWindow(),
                 {
                 {
-                    buttonLabel: 'Select',
+                    buttonLabel: this.translate.instant('Select'),
                     properties,
                     properties,
                 },
                 },
             )
             )

+ 7 - 3
tabby-electron/src/services/updater.service.ts

@@ -2,7 +2,7 @@ import type { AppUpdater } from 'electron-updater'
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
 import axios from 'axios'
 import axios from 'axios'
 
 
-import { Logger, LogService, ConfigService, UpdaterService, PlatformService } from 'tabby-core'
+import { Logger, LogService, ConfigService, UpdaterService, PlatformService, TranslateService } from 'tabby-core'
 import { ElectronService } from '../services/electron.service'
 import { ElectronService } from '../services/electron.service'
 
 
 const UPDATES_URL = 'https://api.github.com/repos/eugeny/tabby/releases/latest'
 const UPDATES_URL = 'https://api.github.com/repos/eugeny/tabby/releases/latest'
@@ -18,6 +18,7 @@ export class ElectronUpdaterService extends UpdaterService {
     constructor (
     constructor (
         log: LogService,
         log: LogService,
         config: ConfigService,
         config: ConfigService,
+        private translate: TranslateService,
         private platform: PlatformService,
         private platform: PlatformService,
         private electron: ElectronService,
         private electron: ElectronService,
     ) {
     ) {
@@ -132,8 +133,11 @@ export class ElectronUpdaterService extends UpdaterService {
             if ((await this.platform.showMessageBox(
             if ((await this.platform.showMessageBox(
                 {
                 {
                     type: 'warning',
                     type: 'warning',
-                    message: 'Installing the update will close all tabs and restart Tabby.',
-                    buttons: ['Update', 'Cancel'],
+                    message: this.translate.instant('Installing the update will close all tabs and restart Tabby.'),
+                    buttons: [
+                        this.translate.instant('Update'),
+                        this.translate.instant('Cancel'),
+                    ],
                     defaultId: 0,
                     defaultId: 0,
                     cancelId: 1,
                     cancelId: 1,
                 }
                 }

+ 4 - 3
tabby-linkifier/src/decorator.ts

@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@angular/core'
 import { Inject, Injectable } from '@angular/core'
-import { ConfigService, PlatformService } from 'tabby-core'
+import { ConfigService, PlatformService, TranslateService } from 'tabby-core'
 import { TerminalDecorator, BaseTerminalTabComponent } from 'tabby-terminal'
 import { TerminalDecorator, BaseTerminalTabComponent } from 'tabby-terminal'
 
 
 import { LinkHandler } from './api'
 import { LinkHandler } from './api'
@@ -9,6 +9,7 @@ export class LinkHighlighterDecorator extends TerminalDecorator {
     constructor (
     constructor (
         private config: ConfigService,
         private config: ConfigService,
         private platform: PlatformService,
         private platform: PlatformService,
+        private translate: TranslateService,
         @Inject(LinkHandler) private handlers: LinkHandler[],
         @Inject(LinkHandler) private handlers: LinkHandler[],
     ) {
     ) {
         super()
         super()
@@ -42,13 +43,13 @@ export class LinkHighlighterDecorator extends TerminalDecorator {
                             this.platform.popupContextMenu([
                             this.platform.popupContextMenu([
                                 {
                                 {
                                     click: () => openLink(uri),
                                     click: () => openLink(uri),
-                                    label: 'Open',
+                                    label: this.translate.instant('Open'),
                                 },
                                 },
                                 {
                                 {
                                     click: async () => {
                                     click: async () => {
                                         this.platform.setClipboard({ text: await getLink(uri) })
                                         this.platform.setClipboard({ text: await getLink(uri) })
                                     },
                                     },
-                                    label: 'Copy',
+                                    label: this.translate.instant('Copy'),
                                 },
                                 },
                             ])
                             ])
                             return false
                             return false

+ 3 - 2
tabby-local/src/buttonProvider.ts

@@ -1,6 +1,6 @@
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
-import { ToolbarButtonProvider, ToolbarButton } from 'tabby-core'
+import { ToolbarButtonProvider, ToolbarButton, TranslateService } from 'tabby-core'
 import { TerminalService } from './services/terminal.service'
 import { TerminalService } from './services/terminal.service'
 
 
 /** @hidden */
 /** @hidden */
@@ -8,6 +8,7 @@ import { TerminalService } from './services/terminal.service'
 export class ButtonProvider extends ToolbarButtonProvider {
 export class ButtonProvider extends ToolbarButtonProvider {
     constructor (
     constructor (
         private terminal: TerminalService,
         private terminal: TerminalService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
     }
     }
@@ -16,7 +17,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
         return [
         return [
             {
             {
                 icon: require('./icons/plus.svg'),
                 icon: require('./icons/plus.svg'),
-                title: 'New terminal',
+                title: this.translate.instant('New terminal'),
                 touchBarNSImage: 'NSTouchBarAddDetailTemplate',
                 touchBarNSImage: 'NSTouchBarAddDetailTemplate',
                 click: () => {
                 click: () => {
                     this.terminal.openTab()
                     this.terminal.openTab()

+ 4 - 4
tabby-local/src/components/commandLineEditor.component.pug

@@ -1,6 +1,6 @@
 ng-container(*ngIf='!argvMode')
 ng-container(*ngIf='!argvMode')
     .form-group
     .form-group
-        label Command line
+        label(translate) Command line
         .input-group
         .input-group
             .input-group-prepend
             .input-group-prepend
                 a.input-group-text(
                 a.input-group-text(
@@ -16,7 +16,7 @@ ng-container(*ngIf='!argvMode')
 
 
 ng-container(*ngIf='argvMode')
 ng-container(*ngIf='argvMode')
     .form-group
     .form-group
-        label Program
+        label(translate) Program
         .input-group
         .input-group
             .input-group-prepend
             .input-group-prepend
                 a.input-group-text(
                 a.input-group-text(
@@ -31,7 +31,7 @@ ng-container(*ngIf='argvMode')
             )
             )
 
 
     .form-group
     .form-group
-        label Arguments
+        label(translate) Arguments
         .input-group(
         .input-group(
             *ngFor='let arg of _model.args; index as i; trackBy: trackByIndex',
             *ngFor='let arg of _model.args; index as i; trackBy: trackByIndex',
         )
         )
@@ -46,4 +46,4 @@ ng-container(*ngIf='argvMode')
         .mt-2
         .mt-2
             button.btn.btn-secondary((click)='_model.args.push("")')
             button.btn.btn-secondary((click)='_model.args.push("")')
                 i.fas.fa-plus.mr-2
                 i.fas.fa-plus.mr-2
-                | Add
+                span(translate) Add

+ 3 - 3
tabby-local/src/components/environmentEditor.component.pug

@@ -11,10 +11,10 @@
 .d-flex
 .d-flex
     button.btn.btn-secondary((click)='addEnvironmentVar()')
     button.btn.btn-secondary((click)='addEnvironmentVar()')
         i.fas.fa-plus.mr-2
         i.fas.fa-plus.mr-2
-        span Add
+        span(translate) Add
 
 
     .ml-auto
     .ml-auto
-    .text-muted Substitutions allowed.
+    .text-muted(translate) Substitutions allowed.
     .d-flex.ml-1(*ngIf='shouldShowExample()')
     .d-flex.ml-1(*ngIf='shouldShowExample()')
-        .text-muted Example:
+        .text-muted(translate) Example:
         a.ml-1((click)='addExample()', href='#') extend PATH
         a.ml-1((click)='addExample()', href='#') extend PATH

+ 3 - 3
tabby-local/src/components/localProfileSettings.component.pug

@@ -2,13 +2,13 @@ command-line-editor([model]='profile.options')
 
 
 .form-line(*ngIf='uac.isAvailable')
 .form-line(*ngIf='uac.isAvailable')
     .header
     .header
-        .title Run as administrator
+        .title(translate) Run as administrator
     toggle(
     toggle(
         [(ngModel)]='profile.options.runAsAdministrator',
         [(ngModel)]='profile.options.runAsAdministrator',
     )
     )
 
 
 .form-group
 .form-group
-    label Working directory
+    label(translate) Working directory
 
 
     .input-group
     .input-group
         input.form-control(
         input.form-control(
@@ -21,7 +21,7 @@ command-line-editor([model]='profile.options')
                 i.fas.fa-folder-open
                 i.fas.fa-folder-open
 
 
 .form-group
 .form-group
-    label Environment
+    label(translate) Environment
     environment-editor(
     environment-editor(
         type='text',
         type='text',
         [(model)]='profile.options.env',
         [(model)]='profile.options.env',

+ 5 - 5
tabby-local/src/components/shellSettingsTab.component.pug

@@ -1,9 +1,9 @@
-h3.mb-3 Shell
+h3.mb-3(translate) Shell
 
 
 .form-line(*ngIf='isConPTYAvailable')
 .form-line(*ngIf='isConPTYAvailable')
     .header
     .header
-        .title Use ConPTY
-        .description Enables the experimental Windows ConPTY API
+        .title(translate) Use ConPTY
+        .description(translate) Enables the experimental Windows ConPTY API
 
 
     toggle(
     toggle(
         [(ngModel)]='config.store.terminal.useConPTY',
         [(ngModel)]='config.store.terminal.useConPTY',
@@ -11,7 +11,7 @@ h3.mb-3 Shell
     )
     )
 
 
 .alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.useConPTY && isConPTYAvailable && !isConPTYStable')
 .alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.useConPTY && isConPTYAvailable && !isConPTYStable')
-    .mr-auto Windows 10 build 18309 or above is recommended for ConPTY
+    .mr-auto(translate) Windows 10 build 18309 or above is recommended for ConPTY
 
 
 .alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.profile.startsWith("WSL") && (!config.store.terminal.useConPTY)')
 .alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.profile.startsWith("WSL") && (!config.store.terminal.useConPTY)')
-    .mr-auto WSL terminal only supports TrueColor with ConPTY
+    .mr-auto(translate) WSL terminal only supports TrueColor with ConPTY

+ 10 - 3
tabby-local/src/components/terminalTab.component.ts

@@ -1,5 +1,5 @@
 import { Component, Input, Injector } from '@angular/core'
 import { Component, Input, Injector } from '@angular/core'
-import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'tabby-core'
+import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, TranslateService } from 'tabby-core'
 import { BaseTerminalTabComponent } from 'tabby-terminal'
 import { BaseTerminalTabComponent } from 'tabby-terminal'
 import { LocalProfile, SessionOptions } from '../api'
 import { LocalProfile, SessionOptions } from '../api'
 import { Session } from '../session'
 import { Session } from '../session'
@@ -20,6 +20,7 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
     // eslint-disable-next-line @typescript-eslint/no-useless-constructor
     // eslint-disable-next-line @typescript-eslint/no-useless-constructor
     constructor (
     constructor (
         injector: Injector,
         injector: Injector,
+        private translate: TranslateService,
         private uac: UACService,
         private uac: UACService,
     ) {
     ) {
         super(injector)
         super(injector)
@@ -108,8 +109,14 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
         return (await this.platform.showMessageBox(
         return (await this.platform.showMessageBox(
             {
             {
                 type: 'warning',
                 type: 'warning',
-                message: `"${children[0].command}" is still running. Close?`,
-                buttons: ['Kill', 'Cancel'],
+                message: this.translate.instant(
+                    '"{command}" is still running. Close?',
+                    children[0],
+                ),
+                buttons: [
+                    this.translate.instant('Kill'),
+                    this.translate.instant('Cancel'),
+                ],
                 defaultId: 0,
                 defaultId: 0,
                 cancelId: 1,
                 cancelId: 1,
             }
             }

+ 4 - 2
tabby-local/src/hotkeys.ts

@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
-import { HotkeyDescription, HotkeyProvider } from 'tabby-core'
+import { HotkeyDescription, HotkeyProvider, TranslateService } from 'tabby-core'
 
 
 /** @hidden */
 /** @hidden */
 @Injectable()
 @Injectable()
@@ -7,10 +7,12 @@ export class LocalTerminalHotkeyProvider extends HotkeyProvider {
     hotkeys: HotkeyDescription[] = [
     hotkeys: HotkeyDescription[] = [
         {
         {
             id: 'new-tab',
             id: 'new-tab',
-            name: 'New tab',
+            name: this.translate.instant('New tab'),
         },
         },
     ]
     ]
 
 
+    constructor (private translate: TranslateService) { super() }
+
     async provide (): Promise<HotkeyDescription[]> {
     async provide (): Promise<HotkeyDescription[]> {
         return this.hotkeys
         return this.hotkeys
     }
     }

+ 3 - 2
tabby-local/src/profiles.ts

@@ -1,6 +1,6 @@
 import deepClone from 'clone-deep'
 import deepClone from 'clone-deep'
 import { Injectable, Inject } from '@angular/core'
 import { Injectable, Inject } from '@angular/core'
-import { ProfileProvider, NewTabParameters, ConfigService, SplitTabComponent, AppService, PartialProfile } from 'tabby-core'
+import { ProfileProvider, NewTabParameters, ConfigService, SplitTabComponent, AppService, PartialProfile, TranslateService } from 'tabby-core'
 import { TerminalTabComponent } from './components/terminalTab.component'
 import { TerminalTabComponent } from './components/terminalTab.component'
 import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
 import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
 import { ShellProvider, Shell, SessionOptions, LocalProfile } from './api'
 import { ShellProvider, Shell, SessionOptions, LocalProfile } from './api'
@@ -8,7 +8,7 @@ import { ShellProvider, Shell, SessionOptions, LocalProfile } from './api'
 @Injectable({ providedIn: 'root' })
 @Injectable({ providedIn: 'root' })
 export class LocalProfilesService extends ProfileProvider<LocalProfile> {
 export class LocalProfilesService extends ProfileProvider<LocalProfile> {
     id = 'local'
     id = 'local'
-    name = 'Local'
+    name = this.translate.instant('Local terminal')
     settingsComponent = LocalProfileSettingsComponent
     settingsComponent = LocalProfileSettingsComponent
     configDefaults = {
     configDefaults = {
         options: {
         options: {
@@ -29,6 +29,7 @@ export class LocalProfilesService extends ProfileProvider<LocalProfile> {
     constructor (
     constructor (
         private app: AppService,
         private app: AppService,
         private config: ConfigService,
         private config: ConfigService,
+        private translate: TranslateService,
         @Inject(ShellProvider) private shellProviders: ShellProvider[],
         @Inject(ShellProvider) private shellProviders: ShellProvider[],
     ) {
     ) {
         super()
         super()

+ 3 - 2
tabby-local/src/services/dockMenu.service.ts

@@ -1,5 +1,5 @@
 import { NgZone, Injectable } from '@angular/core'
 import { NgZone, Injectable } from '@angular/core'
-import { ConfigService, HostAppService, Platform, ProfilesService } from 'tabby-core'
+import { ConfigService, HostAppService, Platform, ProfilesService, TranslateService } from 'tabby-core'
 import { ElectronService } from 'tabby-electron'
 import { ElectronService } from 'tabby-electron'
 
 
 /** @hidden */
 /** @hidden */
@@ -13,6 +13,7 @@ export class DockMenuService {
         private hostApp: HostAppService,
         private hostApp: HostAppService,
         private zone: NgZone,
         private zone: NgZone,
         private profilesService: ProfilesService,
         private profilesService: ProfilesService,
+        private translate: TranslateService,
     ) {
     ) {
         config.changed$.subscribe(() => this.update())
         config.changed$.subscribe(() => this.update())
     }
     }
@@ -21,7 +22,7 @@ export class DockMenuService {
         if (this.hostApp.platform === Platform.Windows) {
         if (this.hostApp.platform === Platform.Windows) {
             this.electron.app.setJumpList(this.config.store.profiles.length ? [{
             this.electron.app.setJumpList(this.config.store.profiles.length ? [{
                 type: 'custom',
                 type: 'custom',
-                name: 'Profiles',
+                name: this.translate.instant('Profiles'),
                 items: this.config.store.profiles.map(profile => ({
                 items: this.config.store.profiles.map(profile => ({
                     type: 'task',
                     type: 'task',
                     program: process.execPath,
                     program: process.execPath,

+ 4 - 3
tabby-local/src/shells/linuxDefault.ts

@@ -1,6 +1,6 @@
 import * as fs from 'mz/fs'
 import * as fs from 'mz/fs'
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
-import { HostAppService, Platform, LogService, Logger } from 'tabby-core'
+import { HostAppService, Platform, LogService, Logger, TranslateService } from 'tabby-core'
 
 
 import { ShellProvider, Shell } from '../api'
 import { ShellProvider, Shell } from '../api'
 
 
@@ -11,6 +11,7 @@ export class LinuxDefaultShellProvider extends ShellProvider {
 
 
     constructor (
     constructor (
         private hostApp: HostAppService,
         private hostApp: HostAppService,
+        private translate: TranslateService,
         log: LogService,
         log: LogService,
     ) {
     ) {
         super()
         super()
@@ -27,14 +28,14 @@ export class LinuxDefaultShellProvider extends ShellProvider {
             this.logger.warn('Could not detect user shell')
             this.logger.warn('Could not detect user shell')
             return [{
             return [{
                 id: 'default',
                 id: 'default',
-                name: 'User default',
+                name: this.translate.instant('User default'),
                 command: '/bin/sh',
                 command: '/bin/sh',
                 env: {},
                 env: {},
             }]
             }]
         } else {
         } else {
             return [{
             return [{
                 id: 'default',
                 id: 'default',
-                name: 'User default',
+                name: this.translate.instant('User default'),
                 command: line.split(':')[6],
                 command: line.split(':')[6],
                 args: ['--login'],
                 args: ['--login'],
                 hidden: true,
                 hidden: true,

+ 3 - 2
tabby-local/src/shells/macDefault.ts

@@ -1,6 +1,6 @@
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
 import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
 import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
-import { HostAppService, Platform } from 'tabby-core'
+import { HostAppService, Platform, TranslateService } from 'tabby-core'
 
 
 import { ShellProvider, Shell } from '../api'
 import { ShellProvider, Shell } from '../api'
 
 
@@ -11,6 +11,7 @@ export class MacOSDefaultShellProvider extends ShellProvider {
 
 
     constructor (
     constructor (
         private hostApp: HostAppService,
         private hostApp: HostAppService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
     }
     }
@@ -21,7 +22,7 @@ export class MacOSDefaultShellProvider extends ShellProvider {
         }
         }
         return [{
         return [{
             id: 'default',
             id: 'default',
-            name: 'OS default',
+            name: this.translate.instant('OS default'),
             command: await this.getDefaultShellCached(),
             command: await this.getDefaultShellCached(),
             args: ['--login'],
             args: ['--login'],
             hidden: true,
             hidden: true,

+ 3 - 2
tabby-local/src/shells/winDefault.ts

@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
-import { HostAppService, Platform } from 'tabby-core'
+import { HostAppService, Platform, TranslateService } from 'tabby-core'
 
 
 import { ShellProvider, Shell } from '../api'
 import { ShellProvider, Shell } from '../api'
 
 
@@ -17,6 +17,7 @@ export class WindowsDefaultShellProvider extends ShellProvider {
         wsl: WSLShellProvider,
         wsl: WSLShellProvider,
         stock: WindowsStockShellsProvider,
         stock: WindowsStockShellsProvider,
         private hostApp: HostAppService,
         private hostApp: HostAppService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
         this.providers = [
         this.providers = [
@@ -39,7 +40,7 @@ export class WindowsDefaultShellProvider extends ShellProvider {
                 return [{
                 return [{
                     ...shell,
                     ...shell,
                     id: 'default',
                     id: 'default',
-                    name: `OS default (${shell.name})`,
+                    name: this.translate.instant('OS default ({name})', shell),
                     hidden: true,
                     hidden: true,
                     env: {},
                     env: {},
                 }]
                 }]

+ 11 - 9
tabby-local/src/tabContextMenu.ts

@@ -1,6 +1,6 @@
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
-import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, SplitTabComponent, NotificationsService, MenuItemOptions, ProfilesService, PromptModalComponent } from 'tabby-core'
+import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, SplitTabComponent, NotificationsService, MenuItemOptions, ProfilesService, PromptModalComponent, TranslateService } from 'tabby-core'
 import { TerminalTabComponent } from './components/terminalTab.component'
 import { TerminalTabComponent } from './components/terminalTab.component'
 import { UACService } from './services/uac.service'
 import { UACService } from './services/uac.service'
 import { TerminalService } from './services/terminal.service'
 import { TerminalService } from './services/terminal.service'
@@ -13,6 +13,7 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
         private config: ConfigService,
         private config: ConfigService,
         private ngbModal: NgbModal,
         private ngbModal: NgbModal,
         private notifications: NotificationsService,
         private notifications: NotificationsService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
     }
     }
@@ -23,10 +24,10 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
         }
         }
         const items: MenuItemOptions[] = [
         const items: MenuItemOptions[] = [
             {
             {
-                label: 'Save as profile',
+                label: this.translate.instant('Save as profile'),
                 click: async () => {
                 click: async () => {
                     const modal = this.ngbModal.open(PromptModalComponent)
                     const modal = this.ngbModal.open(PromptModalComponent)
-                    modal.componentInstance.prompt = 'New profile name'
+                    modal.componentInstance.prompt = this.translate.instant('New profile name')
                     const name = (await modal.result)?.value
                     const name = (await modal.result)?.value
                     if (!name) {
                     if (!name) {
                         return
                         return
@@ -44,7 +45,7 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
                         profile,
                         profile,
                     ]
                     ]
                     this.config.save()
                     this.config.save()
-                    this.notifications.info('Saved')
+                    this.notifications.info(this.translate.instant('Saved'))
                 },
                 },
             },
             },
         ]
         ]
@@ -63,6 +64,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
         private profilesService: ProfilesService,
         private profilesService: ProfilesService,
         private terminalService: TerminalService,
         private terminalService: TerminalService,
         private uac: UACService,
         private uac: UACService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
     }
     }
@@ -72,7 +74,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
 
 
         const items: MenuItemOptions[] = [
         const items: MenuItemOptions[] = [
             {
             {
-                label: 'New terminal',
+                label: this.translate.instant('New terminal'),
                 click: () => {
                 click: () => {
                     if (tab instanceof TerminalTabComponent) {
                     if (tab instanceof TerminalTabComponent) {
                         this.profilesService.openNewTabForProfile(tab.profile)
                         this.profilesService.openNewTabForProfile(tab.profile)
@@ -82,7 +84,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
                 },
                 },
             },
             },
             {
             {
-                label: 'New with profile',
+                label: this.translate.instant('New with profile'),
                 submenu: profiles.map(profile => ({
                 submenu: profiles.map(profile => ({
                     label: profile.name,
                     label: profile.name,
                     click: async () => {
                     click: async () => {
@@ -98,7 +100,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
 
 
         if (this.uac.isAvailable) {
         if (this.uac.isAvailable) {
             items.push({
             items.push({
-                label: 'New admin tab',
+                label: this.translate.instant('New admin tab'),
                 submenu: profiles.map(profile => ({
                 submenu: profiles.map(profile => ({
                     label: profile.name,
                     label: profile.name,
                     click: () => {
                     click: () => {
@@ -116,7 +118,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
 
 
         if (tab instanceof TerminalTabComponent && tabHeader && this.uac.isAvailable) {
         if (tab instanceof TerminalTabComponent && tabHeader && this.uac.isAvailable) {
             items.push({
             items.push({
-                label: 'Duplicate as administrator',
+                label: this.translate.instant('Duplicate as administrator'),
                 click: () => {
                 click: () => {
                     this.profilesService.openNewTabForProfile({
                     this.profilesService.openNewTabForProfile({
                         ...tab.profile,
                         ...tab.profile,
@@ -131,7 +133,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
 
 
         if (tab instanceof TerminalTabComponent && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
         if (tab instanceof TerminalTabComponent && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
             items.push({
             items.push({
-                label: 'Focus all panes',
+                label: this.translate.instant('Focus all panes'),
                 click: () => {
                 click: () => {
                     tab.focusAllPanes()
                     tab.focusAllPanes()
                 },
                 },

+ 18 - 17
tabby-plugin-manager/src/components/pluginsSettingsTab.component.pug

@@ -1,16 +1,16 @@
 .d-flex.mb-3
 .d-flex.mb-3
-    h3 Plugins
+    h3(translate) Plugins
     button.btn.btn-secondary.btn-sm.ml-auto((click)='openPluginsFolder()')
     button.btn.btn-secondary.btn-sm.ml-auto((click)='openPluginsFolder()')
         i.fas.fa-folder
         i.fas.fa-folder
-        span Plugins folder
+        span(translate) Plugins folder
 
 
 .alert.alert-danger(*ngIf='errorMessage')
 .alert.alert-danger(*ngIf='errorMessage')
-    strong Error in {{erroredPlugin}}:
+    strong(translate, [translateParams]='{plugin: erroredPlugin}') Error in {plugin}:
     pre {{errorMessage}}
     pre {{errorMessage}}
 
 
 ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
 ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
     li(ngbNavItem)
     li(ngbNavItem)
-        a(ngbNavLink) Available
+        a(ngbNavLink, translate) Available
         ng-template(ngbNavContent)
         ng-template(ngbNavContent)
             .input-group.mb-3.mt-3
             .input-group.mb-3.mt-3
                 .input-group-prepend
                 .input-group-prepend
@@ -40,14 +40,14 @@ ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
                                     )
                                     )
                                         i.fas.fa-fw.fa-cloud-download(*ngIf='busy.get(plugin.name) != BusyState.Installing')
                                         i.fas.fa-fw.fa-cloud-download(*ngIf='busy.get(plugin.name) != BusyState.Installing')
                                         i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy.get(plugin.name) == BusyState.Installing')
                                         i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy.get(plugin.name) == BusyState.Installing')
-                                        span.ml-2 Get
+                                        span.ml-2(translate) Get
 
 
                                     button.btn.btn-secondary.btn-block.justify-content-center(
                                     button.btn.btn-secondary.btn-block.justify-content-center(
                                         *ngIf='plugin.homepage',
                                         *ngIf='plugin.homepage',
                                         (click)='showPluginHomepage(plugin)'
                                         (click)='showPluginHomepage(plugin)'
                                     )
                                     )
                                         i.fas.fa-fw.fa-external-link-alt
                                         i.fas.fa-fw.fa-external-link-alt
-                                        span.ml-2 Homepage
+                                        span.ml-2(translate) Homepage
 
 
                                 .col-8
                                 .col-8
                                     ng-container(*ngTemplateOutlet='pluginInfo; context: { plugin }')
                                     ng-container(*ngTemplateOutlet='pluginInfo; context: { plugin }')
@@ -55,7 +55,7 @@ ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
                             .mt-2 {{plugin.description}}
                             .mt-2 {{plugin.description}}
 
 
     li(ngbNavItem)
     li(ngbNavItem)
-        a(ngbNavLink) Installed
+        a(ngbNavLink, translate) Installed
         ng-template(ngbNavContent)
         ng-template(ngbNavContent)
             ngb-accordion.mb-4([closeOthers]='true')
             ngb-accordion.mb-4([closeOthers]='true')
                 ng-container(*ngFor='let plugin of pluginManager.installedPlugins')
                 ng-container(*ngFor='let plugin of pluginManager.installedPlugins')
@@ -64,8 +64,8 @@ ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
                             .text-left.mr-auto
                             .text-left.mr-auto
                                 div
                                 div
                                     strong {{plugin.name}}
                                     strong {{plugin.name}}
-                                    small.text-muted.ml-2(*ngIf='plugin.isBuiltin') Built-in
-                                    small.text-warning.ml-2(*ngIf='!isPluginEnabled(plugin)') Disabled
+                                    small.text-muted.ml-2(*ngIf='plugin.isBuiltin', translate) Built-in
+                                    small.text-warning.ml-2(*ngIf='!isPluginEnabled(plugin)', translate) Disabled
                                 small.d-block.text-muted {{plugin.description}}
                                 small.d-block.text-muted {{plugin.description}}
 
 
                             button.btn.btn-primary.ml-2(
                             button.btn.btn-primary.ml-2(
@@ -75,7 +75,7 @@ ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
                             )
                             )
                                 i.fas.fa-fw.fa-arrow-up(*ngIf='busy.get(plugin.name) != BusyState.Installing')
                                 i.fas.fa-fw.fa-arrow-up(*ngIf='busy.get(plugin.name) != BusyState.Installing')
                                 i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy.get(plugin.name) == BusyState.Installing')
                                 i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy.get(plugin.name) == BusyState.Installing')
-                                span Upgrade to {{knownUpgrades[plugin.name].version}}
+                                span(translate, [translateParams]='{version: knownUpgrades[plugin.name].version}') Upgrade to {version}
 
 
                         ng-template(ngbPanelContent)
                         ng-template(ngbPanelContent)
                             .row
                             .row
@@ -83,12 +83,14 @@ ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
                                     button.btn.btn-warning.btn-block.justify-content-center(
                                     button.btn.btn-warning.btn-block.justify-content-center(
                                         (click)='togglePlugin(plugin)',
                                         (click)='togglePlugin(plugin)',
                                         *ngIf='isPluginEnabled(plugin)',
                                         *ngIf='isPluginEnabled(plugin)',
-                                        [disabled]='!canDisablePlugin(plugin)'
+                                        [disabled]='!canDisablePlugin(plugin)',
+                                        translate
                                     ) Disable
                                     ) Disable
 
 
                                     button.btn.btn-success.btn-block.justify-content-center(
                                     button.btn.btn-success.btn-block.justify-content-center(
                                         (click)='togglePlugin(plugin)',
                                         (click)='togglePlugin(plugin)',
-                                        *ngIf='canDisablePlugin(plugin) && !isPluginEnabled(plugin)'
+                                        *ngIf='canDisablePlugin(plugin) && !isPluginEnabled(plugin)',
+                                        translate
                                     ) Enable
                                     ) Enable
 
 
                                     button.btn.btn-danger.btn-block.justify-content-center(
                                     button.btn.btn-danger.btn-block.justify-content-center(
@@ -98,26 +100,25 @@ ul.nav-tabs.mb-2(ngbNav, #nav='ngbNav')
                                     )
                                     )
                                         i.fas.fa-fw.fa-trash(*ngIf='busy.get(plugin.name) != BusyState.Uninstalling')
                                         i.fas.fa-fw.fa-trash(*ngIf='busy.get(plugin.name) != BusyState.Uninstalling')
                                         i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy.get(plugin.name) == BusyState.Uninstalling')
                                         i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy.get(plugin.name) == BusyState.Uninstalling')
-                                        span Uninstall
+                                        span(translate) Uninstall
                                 .col-8
                                 .col-8
                                     ng-container(*ngTemplateOutlet='pluginInfo; context: { plugin }')
                                     ng-container(*ngTemplateOutlet='pluginInfo; context: { plugin }')
 
 
-
                             .mt-2 {{plugin.description}}
                             .mt-2 {{plugin.description}}
 
 
 ng-template(#pluginInfo, let-plugin='plugin')
 ng-template(#pluginInfo, let-plugin='plugin')
     .row.align-items-center
     .row.align-items-center
         .col-4
         .col-4
-            strong Version
+            strong(translate) Version
         .col-8
         .col-8
             span {{plugin.version}}
             span {{plugin.version}}
     .row.align-items-center
     .row.align-items-center
         .col-4
         .col-4
-            strong Author
+            strong(translate) Author
         .col-8
         .col-8
             .badge.badge-success(*ngIf='plugin.isOfficial')
             .badge.badge-success(*ngIf='plugin.isOfficial')
                 i.fas.fa-check
                 i.fas.fa-check
-                span.ml-1 Official
+                span.ml-1(translate) Official
             a.btn.btn-link.px-0.w-auto((click)='showPluginInfo(plugin)', *ngIf='!plugin.isOfficial')
             a.btn.btn-link.px-0.w-auto((click)='showPluginInfo(plugin)', *ngIf='!plugin.isOfficial')
                 span {{plugin.author}}
                 span {{plugin.author}}
                 i.fas.fa-fw.fa-external-link-alt.ml-2
                 i.fas.fa-fw.fa-external-link-alt.ml-2

+ 11 - 11
tabby-serial/src/components/serialProfileSettings.component.pug

@@ -1,11 +1,11 @@
 ul.nav-tabs(ngbNav, #nav='ngbNav')
 ul.nav-tabs(ngbNav, #nav='ngbNav')
     li(ngbNavItem)
     li(ngbNavItem)
-        a(ngbNavLink) General
+        a(ngbNavLink, translate) General
         ng-template(ngbNavContent)
         ng-template(ngbNavContent)
             .row
             .row
                 .col-6(ng:if='hostApp.platform !== Platform.Web')
                 .col-6(ng:if='hostApp.platform !== Platform.Web')
                     .form-group
                     .form-group
-                        label Device
+                        label(translate) Device
                         input.form-control(
                         input.form-control(
                             type='text',
                             type='text',
                             alwaysVisibleTypeahead,
                             alwaysVisibleTypeahead,
@@ -16,7 +16,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
 
 
                 .col-6
                 .col-6
                     .form-group
                     .form-group
-                        label Baud Rate
+                        label(translate) Baud rate
                         input.form-control(
                         input.form-control(
                             type='number',
                             type='number',
                             alwaysVisibleTypeahead,
                             alwaysVisibleTypeahead,
@@ -28,11 +28,11 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
             stream-processing-settings([options]='profile.options')
             stream-processing-settings([options]='profile.options')
 
 
     li(ngbNavItem)
     li(ngbNavItem)
-        a(ngbNavLink) Advanced
+        a(ngbNavLink, translate) Advanced
         ng-template(ngbNavContent)
         ng-template(ngbNavContent)
             .form-line
             .form-line
                 .header
                 .header
-                    .title Data bits
+                    .title(translate) Data bits
                 input.form-control(
                 input.form-control(
                     type='number',
                     type='number',
                     placeholder='8',
                     placeholder='8',
@@ -41,7 +41,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
 
 
             .form-line
             .form-line
                 .header
                 .header
-                    .title Stop bits
+                    .title(translate) Stop bits
                 input.form-control(
                 input.form-control(
                     type='number',
                     type='number',
                     placeholder='1',
                     placeholder='1',
@@ -50,12 +50,12 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
 
 
             .form-line
             .form-line
                 .header
                 .header
-                    .title Parity
+                    .title(translate) Parity
                 select.form-control(
                 select.form-control(
                     type='text',
                     type='text',
                     [(ngModel)]='profile.options.parity'
                     [(ngModel)]='profile.options.parity'
                 )
                 )
-                    option(value='none') None
+                    option(value='none', translate) None
                     option(value='even') Even
                     option(value='even') Even
                     option(value='odd') Odd
                     option(value='odd') Odd
                     option(value='mark', ng:if='hostApp.platform === Platform.Windows') Mark
                     option(value='mark', ng:if='hostApp.platform === Platform.Windows') Mark
@@ -83,12 +83,12 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
 
 
             .form-line
             .form-line
                 .header
                 .header
-                    .title Slow feed
-                    .description Sends data one byte at a time
+                    .title(translate) Slow feed
+                    .description(translate) Sends data one byte at a time
                 toggle([(ngModel)]='profile.options.slowFeed')
                 toggle([(ngModel)]='profile.options.slowFeed')
 
 
     li(ngbNavItem)
     li(ngbNavItem)
-        a(ngbNavLink) Login scripts
+        a(ngbNavLink, translate) Login scripts
         ng-template(ngbNavContent)
         ng-template(ngbNavContent)
             login-scripts-settings([options]='profile.options')
             login-scripts-settings([options]='profile.options')
 
 

+ 2 - 2
tabby-serial/src/components/serialTab.component.pug

@@ -9,8 +9,8 @@
     .mr-auto
     .mr-auto
 
 
     button.btn.btn-sm.btn-link.mr-3((click)='changeBaudRate()', *ngIf='session && session.open && hostApp.platform !== Platform.Web')
     button.btn.btn-sm.btn-link.mr-3((click)='changeBaudRate()', *ngIf='session && session.open && hostApp.platform !== Platform.Web')
-        span Change baud rate
+        span(translate) Change baud rate
 
 
     button.btn.btn-sm.btn-link((click)='reconnect()', *ngIf='!session || !session.open')
     button.btn.btn-sm.btn-link((click)='reconnect()', *ngIf='!session || !session.open')
         i.fas.fa-redo
         i.fas.fa-redo
-        span Reconnect
+        span(translate) Reconnect

+ 11 - 8
tabby-serial/src/components/serialTab.component.ts

@@ -2,7 +2,7 @@
 import colors from 'ansi-colors'
 import colors from 'ansi-colors'
 import { Component, Injector } from '@angular/core'
 import { Component, Injector } from '@angular/core'
 import { first } from 'rxjs'
 import { first } from 'rxjs'
-import { Platform, SelectorService } from 'tabby-core'
+import { Platform, SelectorService, TranslateService } from 'tabby-core'
 import { BaseTerminalTabComponent } from 'tabby-terminal'
 import { BaseTerminalTabComponent } from 'tabby-terminal'
 import { SerialSession, BAUD_RATES, SerialProfile } from '../api'
 import { SerialSession, BAUD_RATES, SerialProfile } from '../api'
 
 
@@ -23,6 +23,7 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
     constructor (
     constructor (
         injector: Injector,
         injector: Injector,
         private selector: SelectorService,
         private selector: SelectorService,
+        private translate: TranslateService,
     ) {
     ) {
         super(injector)
         super(injector)
         this.enableToolbar = true
         this.enableToolbar = true
@@ -67,14 +68,13 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
 
 
         const session = new SerialSession(this.injector, this.profile)
         const session = new SerialSession(this.injector, this.profile)
         this.setSession(session)
         this.setSession(session)
-        this.write(`Connecting to `)
 
 
-        this.startSpinner('Connecting')
+        this.startSpinner(this.translate.instant('Connecting'))
 
 
         try {
         try {
             await this.session!.start()
             await this.session!.start()
             this.stopSpinner()
             this.stopSpinner()
-            session.emitServiceMessage('Port opened')
+            session.emitServiceMessage(this.translate.instant('Port opened'))
         } catch (e) {
         } catch (e) {
             this.stopSpinner()
             this.stopSpinner()
             this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
             this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
@@ -89,7 +89,7 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
             this.session?.resize(this.size.columns, this.size.rows)
             this.session?.resize(this.size.columns, this.size.rows)
         })
         })
         this.attachSessionHandler(this.session!.destroyed$, () => {
         this.attachSessionHandler(this.session!.destroyed$, () => {
-            this.write('Press any key to reconnect\r\n')
+            this.write(this.translate.instant('Press any key to reconnect') + '\r\n')
             this.input$.pipe(first()).subscribe(() => {
             this.input$.pipe(first()).subscribe(() => {
                 if (!this.session?.open) {
                 if (!this.session?.open) {
                     this.reconnect()
                     this.reconnect()
@@ -114,9 +114,12 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
     }
     }
 
 
     async changeBaudRate () {
     async changeBaudRate () {
-        const rate = await this.selector.show('Baud rate', BAUD_RATES.map(x => ({
-            name: x.toString(), result: x,
-        })))
+        const rate = await this.selector.show(
+            this.translate.instant('Baud rate'),
+            BAUD_RATES.map(x => ({
+                name: x.toString(), result: x,
+            })),
+        )
         this.serialPort.update({ baudRate: rate })
         this.serialPort.update({ baudRate: rate })
         this.profile!.options.baudrate = rate
         this.profile!.options.baudrate = rate
     }
     }

+ 5 - 3
tabby-serial/src/hotkeys.ts

@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
-import { HotkeyDescription, HotkeyProvider } from 'tabby-core'
+import { HotkeyDescription, HotkeyProvider, TranslateService } from 'tabby-core'
 
 
 /** @hidden */
 /** @hidden */
 @Injectable()
 @Injectable()
@@ -7,14 +7,16 @@ export class SerialHotkeyProvider extends HotkeyProvider {
     hotkeys: HotkeyDescription[] = [
     hotkeys: HotkeyDescription[] = [
         {
         {
             id: 'serial',
             id: 'serial',
-            name: 'Show Serial connections',
+            name: this.translate.instant('Show Serial connections'),
         },
         },
         {
         {
             id: 'restart-serial-session',
             id: 'restart-serial-session',
-            name: 'Restart current serial session',
+            name: this.translate.instant('Restart current serial session'),
         },
         },
     ]
     ]
 
 
+    constructor (private translate: TranslateService) { super() }
+
     async provide (): Promise<HotkeyDescription[]> {
     async provide (): Promise<HotkeyDescription[]> {
         return this.hotkeys
         return this.hotkeys
     }
     }

+ 14 - 8
tabby-serial/src/profiles.ts

@@ -3,7 +3,7 @@ import SerialPort from 'serialport'
 import WSABinding from 'serialport-binding-webserialapi'
 import WSABinding from 'serialport-binding-webserialapi'
 import deepClone from 'clone-deep'
 import deepClone from 'clone-deep'
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
-import { ProfileProvider, NewTabParameters, SelectorService, HostAppService, Platform } from 'tabby-core'
+import { ProfileProvider, NewTabParameters, SelectorService, HostAppService, Platform, TranslateService } from 'tabby-core'
 import { SerialProfileSettingsComponent } from './components/serialProfileSettings.component'
 import { SerialProfileSettingsComponent } from './components/serialProfileSettings.component'
 import { SerialTabComponent } from './components/serialTab.component'
 import { SerialTabComponent } from './components/serialTab.component'
 import { SerialService } from './services/serial.service'
 import { SerialService } from './services/serial.service'
@@ -12,7 +12,7 @@ import { BAUD_RATES, SerialProfile } from './api'
 @Injectable({ providedIn: 'root' })
 @Injectable({ providedIn: 'root' })
 export class SerialProfilesService extends ProfileProvider<SerialProfile> {
 export class SerialProfilesService extends ProfileProvider<SerialProfile> {
     id = 'serial'
     id = 'serial'
-    name = 'Serial'
+    name = this.translate.instant('Serial')
     settingsComponent = SerialProfileSettingsComponent
     settingsComponent = SerialProfileSettingsComponent
     configDefaults = {
     configDefaults = {
         options: {
         options: {
@@ -38,6 +38,7 @@ export class SerialProfilesService extends ProfileProvider<SerialProfile> {
         private selector: SelectorService,
         private selector: SelectorService,
         private serial: SerialService,
         private serial: SerialService,
         private hostApp: HostAppService,
         private hostApp: HostAppService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
         if (hostApp.platform === Platform.Web) {
         if (hostApp.platform === Platform.Web) {
@@ -51,7 +52,7 @@ export class SerialProfilesService extends ProfileProvider<SerialProfile> {
                 {
                 {
                     id: `serial:web`,
                     id: `serial:web`,
                     type: 'serial',
                     type: 'serial',
-                    name: 'Serial connection',
+                    name: this.translate.instant('Serial connection'),
                     icon: 'fas fa-microchip',
                     icon: 'fas fa-microchip',
                     isBuiltin: true,
                     isBuiltin: true,
                 } as SerialProfile,
                 } as SerialProfile,
@@ -62,7 +63,7 @@ export class SerialProfilesService extends ProfileProvider<SerialProfile> {
             {
             {
                 id: `serial:template`,
                 id: `serial:template`,
                 type: 'serial',
                 type: 'serial',
-                name: 'Serial connection',
+                name: this.translate.instant('Serial connection'),
                 icon: 'fas fa-microchip',
                 icon: 'fas fa-microchip',
                 isBuiltin: true,
                 isBuiltin: true,
                 isTemplate: true,
                 isTemplate: true,
@@ -70,7 +71,9 @@ export class SerialProfilesService extends ProfileProvider<SerialProfile> {
             ...(await this.serial.listPorts()).map(p => ({
             ...(await this.serial.listPorts()).map(p => ({
                 id: `serial:port-${slugify(p.name).replace('.', '-')}`,
                 id: `serial:port-${slugify(p.name).replace('.', '-')}`,
                 type: 'serial',
                 type: 'serial',
-                name: p.description ? `Serial: ${p.description}` : 'Serial',
+                name: p.description ?
+                    this.translate.instant('Serial: {description}', p) :
+                    this.translate.instant('Serial'),
                 icon: 'fas fa-microchip',
                 icon: 'fas fa-microchip',
                 isBuiltin: true,
                 isBuiltin: true,
                 options: {
                 options: {
@@ -83,9 +86,12 @@ export class SerialProfilesService extends ProfileProvider<SerialProfile> {
     async getNewTabParameters (profile: SerialProfile): Promise<NewTabParameters<SerialTabComponent>> {
     async getNewTabParameters (profile: SerialProfile): Promise<NewTabParameters<SerialTabComponent>> {
         if (!profile.options.baudrate) {
         if (!profile.options.baudrate) {
             profile = deepClone(profile)
             profile = deepClone(profile)
-            profile.options.baudrate = await this.selector.show('Baud rate', BAUD_RATES.map(x => ({
-                name: x.toString(), result: x,
-            })))
+            profile.options.baudrate = await this.selector.show(
+                this.translate.instant('Baud rate'),
+                BAUD_RATES.map(x => ({
+                    name: x.toString(), result: x,
+                })),
+            )
         }
         }
         return {
         return {
             type: SerialTabComponent,
             type: SerialTabComponent,

+ 3 - 2
tabby-settings/src/buttonProvider.ts

@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
-import { ToolbarButtonProvider, ToolbarButton, AppService, HostAppService, HotkeysService } from 'tabby-core'
+import { ToolbarButtonProvider, ToolbarButton, AppService, HostAppService, HotkeysService, TranslateService } from 'tabby-core'
 
 
 import { SettingsTabComponent } from './components/settingsTab.component'
 import { SettingsTabComponent } from './components/settingsTab.component'
 
 
@@ -10,6 +10,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
         hostApp: HostAppService,
         hostApp: HostAppService,
         hotkeys: HotkeysService,
         hotkeys: HotkeysService,
         private app: AppService,
         private app: AppService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
         hostApp.settingsUIRequest$.subscribe(() => this.open())
         hostApp.settingsUIRequest$.subscribe(() => this.open())
@@ -24,7 +25,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
     provide (): ToolbarButton[] {
     provide (): ToolbarButton[] {
         return [{
         return [{
             icon: require('./icons/cog.svg'),
             icon: require('./icons/cog.svg'),
-            title: 'Settings',
+            title: this.translate.instant('Settings'),
             touchBarNSImage: 'NSTouchBarComposeTemplate',
             touchBarNSImage: 'NSTouchBarComposeTemplate',
             weight: 10,
             weight: 10,
             click: (): void => this.open(),
             click: (): void => this.open(),

+ 19 - 19
tabby-settings/src/components/configSyncSettingsTab.component.pug

@@ -1,12 +1,12 @@
-h3.mb-3 Config sync
+h3.mb-3(translate) Config sync
 
 
 ul.nav-tabs(ngbNav, #nav='ngbNav')
 ul.nav-tabs(ngbNav, #nav='ngbNav')
     li(ngbNavItem)
     li(ngbNavItem)
-        a(ngbNavLink) Sync
+        a(ngbNavLink, translate) Sync
         ng-template(ngbNavContent)
         ng-template(ngbNavContent)
             .form-line
             .form-line
                 .header
                 .header
-                    .title Sync host
+                    .title(translate) Sync host
 
 
                 .input-group.w-50
                 .input-group.w-50
                     input.form-control(
                     input.form-control(
@@ -20,8 +20,8 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
 
 
             .form-line
             .form-line
                 .header
                 .header
-                    .title Secret sync token
-                    .description Get it from the Tabby Web settings window
+                    .title(translate) Secret sync token
+                    .description(translate) Get it from the Tabby Web settings window
 
 
                 .input-group
                 .input-group
                     input.form-control(
                     input.form-control(
@@ -38,16 +38,16 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
             ng-container(*ngIf='config.store.configSync.token')
             ng-container(*ngIf='config.store.configSync.token')
                 .alert.alert-danger(*ngIf='connectionSuccessful === false')
                 .alert.alert-danger(*ngIf='connectionSuccessful === false')
                     i.fas.fa-exclamation-triangle
                     i.fas.fa-exclamation-triangle
-                    span.ml-2 Connection failed: {{connectionError}}
+                    span.ml-2(translate, [translateParams]='{error: connectionError}') Connection failed: {error}
 
 
             ng-container(*ngIf='connectionSuccessful')
             ng-container(*ngIf='connectionSuccessful')
                 .form-line
                 .form-line
                     .header
                     .header
-                        .title Configs
+                        .title(translate) Configs
 
 
                 div(*ngIf='configs === null')
                 div(*ngIf='configs === null')
                     i.fas.fa-fw.fa-circle-notch.fa-spin
                     i.fas.fa-fw.fa-circle-notch.fa-spin
-                    span.ml-2 Loading configs...
+                    span.ml-2(translate) Loading configs...
 
 
                 ng-container(*ngIf='configs !== null')
                 ng-container(*ngIf='configs !== null')
                     .list-group-light
                     .list-group-light
@@ -59,34 +59,34 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
                             i.fas.fa-fw.fa-file
                             i.fas.fa-fw.fa-file
                             .ml-2.d-flex.flex-column.align-items-start
                             .ml-2.d-flex.flex-column.align-items-start
                                 div {{cfg.name}}
                                 div {{cfg.name}}
-                                small.text-muted Modified on {{cfg.modified_at|date:'medium'}}
+                                small.text-muted(translate, [translateParams]='{date: cfg.modified_at|date:"medium"}') Modified on {date}
                             .mr-auto
                             .mr-auto
                             button.btn.btn-link.ml-1(
                             button.btn.btn-link.ml-1(
                                 (click)='uploadAndSync(cfg)',
                                 (click)='uploadAndSync(cfg)',
                                 [class.hover-reveal]='!isActiveConfig(cfg)'
                                 [class.hover-reveal]='!isActiveConfig(cfg)'
                             )
                             )
                                 i.fas.fa-arrow-up
                                 i.fas.fa-arrow-up
-                                span.ml-2(*ngIf='isActiveConfig(cfg)') Upload
-                                span.ml-2(*ngIf='!isActiveConfig(cfg)') Replace
+                                span.ml-2(*ngIf='isActiveConfig(cfg)', translate) Upload
+                                span.ml-2(*ngIf='!isActiveConfig(cfg)', translate) Replace
                             button.btn.btn-link.ml-1(
                             button.btn.btn-link.ml-1(
                                 (click)='downloadAndSync(cfg)',
                                 (click)='downloadAndSync(cfg)',
                                 [class.hover-reveal]='!isActiveConfig(cfg)'
                                 [class.hover-reveal]='!isActiveConfig(cfg)'
                             )
                             )
                                 i.fas.fa-arrow-down
                                 i.fas.fa-arrow-down
-                                span.ml-2 Download
+                                span.ml-2(translate) Download
                         a.list-group-item.list-group-item-action.d-flex.align-items-center(
                         a.list-group-item.list-group-item-action.d-flex.align-items-center(
                             href='#',
                             href='#',
                             (click)='uploadAsNew()'
                             (click)='uploadAsNew()'
                         )
                         )
                             i.fas.fa-fw
                             i.fas.fa-fw
                             i.fas.fa-fw.fa-cloud-upload-alt
                             i.fas.fa-fw.fa-cloud-upload-alt
-                            .ml-2 Upload as a new config
+                            .ml-2(translate) Upload as a new config
 
 
             ng-container(*ngIf='hasMatchingRemoteConfig()')
             ng-container(*ngIf='hasMatchingRemoteConfig()')
                 .form-line
                 .form-line
                     .header
                     .header
-                        .title Sync automatically
-                        .description Automatically upload changes and check for updates every minute
+                        .title(translate) Sync automatically
+                        .description(translate) Automatically upload changes and check for updates every minute
 
 
                     toggle(
                     toggle(
                         [(ngModel)]='config.store.configSync.auto',
                         [(ngModel)]='config.store.configSync.auto',
@@ -94,11 +94,11 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
                     )
                     )
 
 
     li(ngbNavItem)
     li(ngbNavItem)
-        a(ngbNavLink) Advanced
+        a(ngbNavLink, translate) Advanced
         ng-template(ngbNavContent)
         ng-template(ngbNavContent)
             .form-line
             .form-line
                 .header
                 .header
-                    .title Sync hotkeys
+                    .title(translate) Sync hotkeys
                 toggle(
                 toggle(
                     [(ngModel)]='config.store.configSync.parts.hotkeys',
                     [(ngModel)]='config.store.configSync.parts.hotkeys',
                     (ngModelChange)='config.save()',
                     (ngModelChange)='config.save()',
@@ -106,7 +106,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
 
 
             .form-line
             .form-line
                 .header
                 .header
-                    .title Sync window settings
+                    .title(translate) Sync window settings
                 toggle(
                 toggle(
                     [(ngModel)]='config.store.configSync.parts.appearance',
                     [(ngModel)]='config.store.configSync.parts.appearance',
                     (ngModelChange)='config.save()',
                     (ngModelChange)='config.save()',
@@ -114,7 +114,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
 
 
             .form-line
             .form-line
                 .header
                 .header
-                    .title Sync Vault
+                    .title(translate) Sync Vault
                 toggle(
                 toggle(
                     [(ngModel)]='config.store.configSync.parts.vault',
                     [(ngModel)]='config.store.configSync.parts.vault',
                     (ngModelChange)='config.save()',
                     (ngModelChange)='config.save()',

+ 16 - 9
tabby-settings/src/components/configSyncSettingsTab.component.ts

@@ -1,7 +1,7 @@
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 import { Component, HostBinding } from '@angular/core'
 import { Component, HostBinding } from '@angular/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
-import { BaseComponent, ConfigService, PromptModalComponent, HostAppService, PlatformService, NotificationsService } from 'tabby-core'
+import { BaseComponent, ConfigService, PromptModalComponent, HostAppService, PlatformService, NotificationsService, TranslateService } from 'tabby-core'
 import { Config, ConfigSyncService } from '../services/configSync.service'
 import { Config, ConfigSyncService } from '../services/configSync.service'
 
 
 
 
@@ -24,6 +24,7 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
         private hostApp: HostAppService,
         private hostApp: HostAppService,
         private ngbModal: NgbModal,
         private ngbModal: NgbModal,
         private notifications: NotificationsService,
         private notifications: NotificationsService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
     }
     }
@@ -54,9 +55,9 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
     }
     }
 
 
     async uploadAsNew () {
     async uploadAsNew () {
-        let name = `New config on ${this.hostApp.platform}`
+        let name = this.translate.instant('New config on {platform}', this.hostApp)
         const modal = this.ngbModal.open(PromptModalComponent)
         const modal = this.ngbModal.open(PromptModalComponent)
-        modal.componentInstance.prompt = 'Name for the new config'
+        modal.componentInstance.prompt = this.translate.instant('Name for the new config')
         modal.componentInstance.value = name
         modal.componentInstance.value = name
         name = (await modal.result)?.value
         name = (await modal.result)?.value
         if (!name) {
         if (!name) {
@@ -72,8 +73,11 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
         if (this.config.store.configSync.configID !== cfg.id) {
         if (this.config.store.configSync.configID !== cfg.id) {
             if ((await this.platform.showMessageBox({
             if ((await this.platform.showMessageBox({
                 type: 'warning',
                 type: 'warning',
-                message: 'Overwrite the config on the remote side and start syncing?',
-                buttons: ['Overwrite remote and sync', 'Cancel'],
+                message: this.translate.instant('Overwrite the config on the remote side and start syncing?'),
+                buttons: [
+                    this.translate.instant('Overwrite remote and sync'),
+                    this.translate.instant('Cancel'),
+                ],
                 defaultId: 1,
                 defaultId: 1,
                 cancelId: 1,
                 cancelId: 1,
             })).response === 1) {
             })).response === 1) {
@@ -83,14 +87,17 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
         this.configSync.setConfig(cfg)
         this.configSync.setConfig(cfg)
         await this.configSync.upload()
         await this.configSync.upload()
         this.loadConfigs()
         this.loadConfigs()
-        this.notifications.info('Config uploaded')
+        this.notifications.info(this.translate.instant('Config uploaded'))
     }
     }
 
 
     async downloadAndSync (cfg: Config) {
     async downloadAndSync (cfg: Config) {
         if ((await this.platform.showMessageBox({
         if ((await this.platform.showMessageBox({
             type: 'warning',
             type: 'warning',
-            message: 'Overwrite the local config and start syncing?',
-            buttons: ['Overwrite local and sync', 'Cancel'],
+            message: this.translate.instant('Overwrite the local config and start syncing?'),
+            buttons: [
+                this.translate.instant('Overwrite local and sync'),
+                this.translate.instant('Cancel'),
+            ],
             defaultId: 1,
             defaultId: 1,
             cancelId: 1,
             cancelId: 1,
         })).response === 1) {
         })).response === 1) {
@@ -98,7 +105,7 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
         }
         }
         this.configSync.setConfig(cfg)
         this.configSync.setConfig(cfg)
         await this.configSync.download()
         await this.configSync.download()
-        this.notifications.info('Config downloaded')
+        this.notifications.info(this.translate.instant('Config downloaded'))
     }
     }
 
 
     hasMatchingRemoteConfig () {
     hasMatchingRemoteConfig () {

+ 9 - 9
tabby-settings/src/components/editProfileModal.component.pug

@@ -2,13 +2,13 @@
     h3.m-0 {{profile.name}}
     h3.m-0 {{profile.name}}
 
 
 .modal-header(*ngIf='defaultsMode')
 .modal-header(*ngIf='defaultsMode')
-    h3.m-0 Defaults for {{profileProvider.name}}
+    h3.m-0(translate, [translateParams]='{type: profileProvider.name}') Defaults for {type}
 
 
 .modal-body
 .modal-body
     .row
     .row
         .col-12.col-lg-4
         .col-12.col-lg-4
             .form-group(*ngIf='!defaultsMode')
             .form-group(*ngIf='!defaultsMode')
-                label Name
+                label(translate) Name
                 input.form-control(
                 input.form-control(
                     type='text',
                     type='text',
                     autofocus,
                     autofocus,
@@ -16,7 +16,7 @@
                 )
                 )
 
 
             .form-group(*ngIf='!defaultsMode')
             .form-group(*ngIf='!defaultsMode')
-                label Group
+                label(translate) Group
                 input.form-control(
                 input.form-control(
                     type='text',
                     type='text',
                     alwaysVisibleTypeahead,
                     alwaysVisibleTypeahead,
@@ -26,7 +26,7 @@
                 )
                 )
 
 
             .form-group(*ngIf='!defaultsMode')
             .form-group(*ngIf='!defaultsMode')
-                label Icon
+                label(translate) Icon
                 .input-group
                 .input-group
                     input.form-control(
                     input.form-control(
                         type='text',
                         type='text',
@@ -45,7 +45,7 @@
 
 
             .form-line
             .form-line
                 .header
                 .header
-                    .title Color
+                    .title(translate) Color
                 input.form-control.w-50(
                 input.form-control.w-50(
                     type='text',
                     type='text',
                     [(ngModel)]='profile.color',
                     [(ngModel)]='profile.color',
@@ -56,8 +56,8 @@
 
 
             .form-line
             .form-line
                 .header
                 .header
-                    .title Disable dynamic tab title
-                    .description Connection name will be used instead
+                    .title(translate) Disable dynamic tab title
+                    .description(translate) Connection name will be used instead
                 toggle([(ngModel)]='profile.disableDynamicTitle')
                 toggle([(ngModel)]='profile.disableDynamicTitle')
 
 
             .mb-4
             .mb-4
@@ -66,5 +66,5 @@
             ng-template(#placeholder)
             ng-template(#placeholder)
 
 
 .modal-footer
 .modal-footer
-    button.btn.btn-primary((click)='save()') Save
-    button.btn.btn-danger((click)='cancel()') Cancel
+    button.btn.btn-primary((click)='save()', translate) Save
+    button.btn.btn-danger((click)='cancel()', translate) Cancel

+ 2 - 2
tabby-settings/src/components/hotkeyInputModal.component.pug

@@ -1,5 +1,5 @@
 .modal-header
 .modal-header
-    h5 Press the key now
+    h5(translate) Press the key now
 
 
 .modal-body
 .modal-body
     .input
     .input
@@ -9,4 +9,4 @@
         div([style.width]='timeoutProgress + "%"')
         div([style.width]='timeoutProgress + "%"')
 
 
 .modal-footer
 .modal-footer
-    button.btn.btn-primary((click)='close()') Cancel
+    button.btn.btn-primary((click)='close()', translate) Cancel

+ 1 - 1
tabby-settings/src/components/hotkeySettingsTab.component.pug

@@ -1,4 +1,4 @@
-h3.mb-3 Hotkeys
+h3.mb-3(translate) Hotkeys
 
 
 .input-group.mb-4
 .input-group.mb-4
     .input-group-prepend
     .input-group-prepend

+ 1 - 1
tabby-settings/src/components/multiHotkeyInput.component.pug

@@ -3,4 +3,4 @@
         .stroke(*ngFor='let stroke of item') {{stroke}}
         .stroke(*ngFor='let stroke of item') {{stroke}}
     .remove((click)='removeItem(item)') &times;
     .remove((click)='removeItem(item)') &times;
 
 
-.add((click)='addItem()') Add...
+.add((click)='addItem()', translate) Add...

+ 12 - 12
tabby-settings/src/components/profilesSettingsTab.component.pug

@@ -1,12 +1,12 @@
-h3.mb-3 Profiles
+h3.mb-3(translate) Profiles
 
 
 ul.nav-tabs(ngbNav, #nav='ngbNav')
 ul.nav-tabs(ngbNav, #nav='ngbNav')
     li(ngbNavItem)
     li(ngbNavItem)
-        a(ngbNavLink) Profiles
+        a(ngbNavLink, translate) Profiles
         ng-template(ngbNavContent)
         ng-template(ngbNavContent)
             .form-line
             .form-line
                 .header
                 .header
-                    .title Default profile for new tabs
+                    .title(translate) Default profile for new tabs
 
 
                 select.form-control(
                 select.form-control(
                     [(ngModel)]='config.store.terminal.profile',
                     [(ngModel)]='config.store.terminal.profile',
@@ -30,7 +30,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
 
 
                 button.btn.btn-primary.flex-shrink-0.ml-3((click)='newProfile()')
                 button.btn.btn-primary.flex-shrink-0.ml-3((click)='newProfile()')
                     i.fas.fa-fw.fa-plus
                     i.fas.fa-fw.fa-plus
-                    | New profile
+                    span(translate) New profile
 
 
             .list-group.mt-3.mb-3
             .list-group.mt-3.mb-3
                 ng-container(*ngFor='let group of profileGroups')
                 ng-container(*ngFor='let group of profileGroups')
@@ -40,7 +40,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
                         )
                         )
                             .fa.fa-fw.fa-chevron-right(*ngIf='group.collapsed')
                             .fa.fa-fw.fa-chevron-right(*ngIf='group.collapsed')
                             .fa.fa-fw.fa-chevron-down(*ngIf='!group.collapsed')
                             .fa.fa-fw.fa-chevron-down(*ngIf='!group.collapsed')
-                            span.ml-3.mr-auto {{group.name || "Ungrouped"}}
+                            span.ml-3.mr-auto {{group.name || ("Ungrouped"|translate)}}
                             button.btn.btn-sm.btn-link.hover-reveal.ml-2(
                             button.btn.btn-sm.btn-link.hover-reveal.ml-2(
                                 *ngIf='group.editable && group.name',
                                 *ngIf='group.editable && group.name',
                                 (click)='$event.stopPropagation(); editGroup(group)'
                                 (click)='$event.stopPropagation(); editGroup(group)'
@@ -88,12 +88,12 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
                                     .ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}}
                                     .ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}}
 
 
     li(ngbNavItem)
     li(ngbNavItem)
-        a(ngbNavLink) Advanced
+        a(ngbNavLink, translate) Advanced
         ng-template(ngbNavContent)
         ng-template(ngbNavContent)
             .form-line(*ngIf='config.store.profiles.length > 0')
             .form-line(*ngIf='config.store.profiles.length > 0')
                 .header
                 .header
-                    .title Show recent profiles in selector
-                    .description Set to 0 to disable recent profiles
+                    .title(translate) Show recent profiles in selector
+                    .description(translate) Set to 0 to disable recent profiles
 
 
                 input.form-control(
                 input.form-control(
                     type='number',
                     type='number',
@@ -105,8 +105,8 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
 
 
             .form-line(*ngIf='config.store.profiles.length > 0')
             .form-line(*ngIf='config.store.profiles.length > 0')
                 .header
                 .header
-                    .title Show built-in profiles in selector
-                    .description If disabled, only custom profiles will show up in the profile selector
+                    .title(translate) Show built-in profiles in selector
+                    .description(translate) If disabled, only custom profiles will show up in the profile selector
 
 
                 toggle(
                 toggle(
                     [(ngModel)]='config.store.terminal.showBuiltinProfiles',
                     [(ngModel)]='config.store.terminal.showBuiltinProfiles',
@@ -115,8 +115,8 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
 
 
             .form-line
             .form-line
                 .header
                 .header
-                    .title Default profile settings
-                    .description These apply to all profiles of a given type
+                    .title(translate) Default profile settings
+                    .description(translate) These apply to all profiles of a given type
 
 
             .list-group.mt-3.mb-3
             .list-group.mt-3.mb-3
                 a.list-group-item.list-group-item-action(
                 a.list-group-item.list-group-item-action(

+ 24 - 14
tabby-settings/src/components/profilesSettingsTab.component.ts

@@ -3,7 +3,7 @@ import slugify from 'slugify'
 import deepClone from 'clone-deep'
 import deepClone from 'clone-deep'
 import { Component, HostBinding, Inject } from '@angular/core'
 import { Component, HostBinding, Inject } from '@angular/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
-import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile, ProfileProvider } from 'tabby-core'
+import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile, ProfileProvider, TranslateService } from 'tabby-core'
 import { EditProfileModalComponent } from './editProfileModal.component'
 import { EditProfileModalComponent } from './editProfileModal.component'
 
 
 interface ProfileGroup {
 interface ProfileGroup {
@@ -35,6 +35,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
         private selector: SelectorService,
         private selector: SelectorService,
         private ngbModal: NgbModal,
         private ngbModal: NgbModal,
         private platform: PlatformService,
         private platform: PlatformService,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
         this.profileProviders.sort((a, b) => a.name.localeCompare(b.name))
         this.profileProviders.sort((a, b) => a.name.localeCompare(b.name))
@@ -58,7 +59,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
             const profiles = [...this.templateProfiles, ...this.builtinProfiles, ...this.profiles]
             const profiles = [...this.templateProfiles, ...this.builtinProfiles, ...this.profiles]
             profiles.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0))
             profiles.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0))
             base = await this.selector.show(
             base = await this.selector.show(
-                'Select a base profile to use as a template',
+                this.translate.instant('Select a base profile to use as a template'),
                 profiles.map(p => ({
                 profiles.map(p => ({
                     icon: p.icon,
                     icon: p.icon,
                     description: this.profilesService.getDescription(p) ?? undefined,
                     description: this.profilesService.getDescription(p) ?? undefined,
@@ -72,14 +73,14 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
         if (base.isTemplate) {
         if (base.isTemplate) {
             profile.name = ''
             profile.name = ''
         } else if (!base.isBuiltin) {
         } else if (!base.isBuiltin) {
-            profile.name = `${base.name} copy`
+            profile.name = this.translate.instant('{name} copy', base)
         }
         }
         profile.isBuiltin = false
         profile.isBuiltin = false
         profile.isTemplate = false
         profile.isTemplate = false
         await this.showProfileEditModal(profile)
         await this.showProfileEditModal(profile)
         if (!profile.name) {
         if (!profile.name) {
             const cfgProxy = this.profilesService.getConfigProxyForProfile(profile)
             const cfgProxy = this.profilesService.getConfigProxyForProfile(profile)
-            profile.name = this.profilesService.providerForProfile(profile)?.getSuggestedName(cfgProxy) ?? `${base.name} copy`
+            profile.name = this.profilesService.providerForProfile(profile)?.getSuggestedName(cfgProxy) ?? this.translate.instant('{name} copy', base)
         }
         }
         profile.id = `${profile.type}:custom:${slugify(profile.name)}:${uuidv4()}`
         profile.id = `${profile.type}:custom:${slugify(profile.name)}:${uuidv4()}`
         this.config.store.profiles = [profile, ...this.config.store.profiles]
         this.config.store.profiles = [profile, ...this.config.store.profiles]
@@ -122,8 +123,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
         if ((await this.platform.showMessageBox(
         if ((await this.platform.showMessageBox(
             {
             {
                 type: 'warning',
                 type: 'warning',
-                message: `Delete "${profile.name}"?`,
-                buttons: ['Delete', 'Keep'],
+                message: this.translate.instant('Delete "{name}"?', profile),
+                buttons: [
+                    this.translate.instant('Delete'),
+                    this.translate.instant('Keep'),
+                ],
                 defaultId: 1,
                 defaultId: 1,
                 cancelId: 1,
                 cancelId: 1,
             }
             }
@@ -156,7 +160,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
         this.profileGroups.sort((a, b) => a.name?.localeCompare(b.name ?? '') ?? -1)
         this.profileGroups.sort((a, b) => a.name?.localeCompare(b.name ?? '') ?? -1)
 
 
         this.profileGroups.push({
         this.profileGroups.push({
-            name: 'Built-in',
+            name: this.translate.instant('Built-in'),
             profiles: this.builtinProfiles,
             profiles: this.builtinProfiles,
             editable: false,
             editable: false,
             collapsed: false,
             collapsed: false,
@@ -165,7 +169,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
 
 
     async editGroup (group: ProfileGroup): Promise<void> {
     async editGroup (group: ProfileGroup): Promise<void> {
         const modal = this.ngbModal.open(PromptModalComponent)
         const modal = this.ngbModal.open(PromptModalComponent)
-        modal.componentInstance.prompt = 'New name'
+        modal.componentInstance.prompt = this.translate.instant('New name')
         modal.componentInstance.value = group.name
         modal.componentInstance.value = group.name
         const result = await modal.result
         const result = await modal.result
         if (result) {
         if (result) {
@@ -181,8 +185,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
         if ((await this.platform.showMessageBox(
         if ((await this.platform.showMessageBox(
             {
             {
                 type: 'warning',
                 type: 'warning',
-                message: `Delete "${group.name}"?`,
-                buttons: ['Delete', 'Keep'],
+                message: this.translate.instant('Delete "{name}"?', group),
+                buttons: [
+                    this.translate.instant('Delete'),
+                    this.translate.instant('Keep'),
+                ],
                 defaultId: 1,
                 defaultId: 1,
                 cancelId: 1,
                 cancelId: 1,
             }
             }
@@ -190,8 +197,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
             if ((await this.platform.showMessageBox(
             if ((await this.platform.showMessageBox(
                 {
                 {
                     type: 'warning',
                     type: 'warning',
-                    message: `Delete the group's profiles?`,
-                    buttons: ['Move to "Ungrouped"', 'Delete'],
+                    message: this.translate.instant(`Delete the group's profiles?`),
+                    buttons: [
+                        this.translate.instant('Move to "Ungrouped"'),
+                        this.translate.instant('Delete'),
+                    ],
                     defaultId: 0,
                     defaultId: 0,
                     cancelId: 0,
                     cancelId: 0,
                 }
                 }
@@ -224,10 +234,10 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
 
 
     getTypeLabel (profile: PartialProfile<Profile>): string {
     getTypeLabel (profile: PartialProfile<Profile>): string {
         const name = this.profilesService.providerForProfile(profile)?.name
         const name = this.profilesService.providerForProfile(profile)?.name
-        if (name === 'Local') {
+        if (name === this.translate.instant('Local terminal')) {
             return ''
             return ''
         }
         }
-        return name ?? 'Unknown'
+        return name ?? this.translate.instant('Unknown')
     }
     }
 
 
     getTypeColorClass (profile: PartialProfile<Profile>): string {
     getTypeColorClass (profile: PartialProfile<Profile>): string {

+ 3 - 3
tabby-settings/src/components/releaseNotesTab.component.ts

@@ -2,7 +2,7 @@
 import axios from 'axios'
 import axios from 'axios'
 import { marked } from 'marked'
 import { marked } from 'marked'
 import { Component } from '@angular/core'
 import { Component } from '@angular/core'
-import { BaseTabComponent } from 'tabby-core'
+import { BaseTabComponent, TranslateService } from 'tabby-core'
 
 
 export interface Release {
 export interface Release {
     name: string
     name: string
@@ -21,9 +21,9 @@ export class ReleaseNotesComponent extends BaseTabComponent {
     releases: Release[] = []
     releases: Release[] = []
     lastPage = 1
     lastPage = 1
 
 
-    constructor () {
+    constructor (translate: TranslateService) {
         super()
         super()
-        this.setTitle('Release notes')
+        this.setTitle(translate.instant('Release notes'))
         this.loadReleases(1)
         this.loadReleases(1)
     }
     }
 
 

+ 4 - 4
tabby-settings/src/components/setVaultPassphraseModal.component.pug

@@ -1,6 +1,6 @@
-h3.modal-header.m-0.pb-0 Set master passphrase
+h3.modal-header.m-0.pb-0(translate) Set master passphrase
 .modal-body
 .modal-body
-    .mb-2 You can change it later, but it's unrecoverable if forgotten.
+    .mb-2(translate) You can change it later, but it's unrecoverable if forgotten.
     .input-group
     .input-group
         input.form-control.form-control-lg(
         input.form-control.form-control-lg(
             [type]='showPassphrase ? "text" : "password"',
             [type]='showPassphrase ? "text" : "password"',
@@ -16,5 +16,5 @@ h3.modal-header.m-0.pb-0 Set master passphrase
                 i.fas.fa-eye
                 i.fas.fa-eye
 
 
 .modal-footer
 .modal-footer
-    button.btn.btn-primary((click)='ok()') Set passphrase
-    button.btn.btn-danger((click)='cancel()') Cancel
+    button.btn.btn-primary((click)='ok()', translate) Set passphrase
+    button.btn.btn-danger((click)='cancel()', translate) Cancel

+ 46 - 31
tabby-settings/src/components/settingsTab.component.pug

@@ -3,7 +3,7 @@
         li(ngbNavItem='application')
         li(ngbNavItem='application')
             a(ngbNavLink)
             a(ngbNavLink)
                 i.fas.fa-fw.fa-window-maximize.mr-2
                 i.fas.fa-fw.fa-window-maximize.mr-2
-                | Application
+                span(translate) Application
             ng-template(ngbNavContent)
             ng-template(ngbNavContent)
                 .content-box
                 .content-box
                     .row
                     .row
@@ -23,59 +23,69 @@
                                     i.fas.fa-sync(
                                     i.fas.fa-sync(
                                         [class.fa-spin]='checkingForUpdate'
                                         [class.fa-spin]='checkingForUpdate'
                                     )
                                     )
-                                    span Check for updates
+                                    span(translate) Check for updates
 
 
                                 button.btn.btn-info.mt-3.mb-2(
                                 button.btn.btn-info.mt-3.mb-2(
                                     *ngIf='updateAvailable',
                                     *ngIf='updateAvailable',
                                     (click)='updater.update()',
                                     (click)='updater.update()',
                                 )
                                 )
                                     i.fas.fa-sync
                                     i.fas.fa-sync
-                                    span Update
+                                    span(translate) Update
 
 
                         .col-12.col-md-6
                         .col-12.col-md-6
                             .list-group.list-group-light.mb-5
                             .list-group.list-group-light.mb-5
                                 button.list-group-item.list-group-item-action.link-card((click)='homeBase.reportBug()')
                                 button.list-group-item.list-group-item-action.link-card((click)='homeBase.reportBug()')
                                     i.fas.fa-fw.fa-bug
                                     i.fas.fa-fw.fa-bug
                                     div
                                     div
-                                        div Report a problem
-                                        small.text-muted Generate a pre-filled GitHub issue
+                                        div(translate) Report a problem
+                                        small.text-muted(translate) Generate a pre-filled GitHub issue
 
 
                                 button.list-group-item.list-group-item-action.link-card((click)='homeBase.openDiscussions()')
                                 button.list-group-item.list-group-item-action.link-card((click)='homeBase.openDiscussions()')
                                     i.fas.fa-fw.fa-comments
                                     i.fas.fa-fw.fa-comments
                                     div
                                     div
-                                        div Ask a question
-                                        small.text-muted On GitHub Discussions
+                                        div(translate) Ask a question
+                                        small.text-muted(translate) On GitHub Discussions
 
 
                                 button.list-group-item.list-group-item-action.link-card((click)='homeBase.openGitHub()')
                                 button.list-group-item.list-group-item-action.link-card((click)='homeBase.openGitHub()')
                                     i.fab.fa-fw.fa-github
                                     i.fab.fa-fw.fa-github
                                     div
                                     div
                                         div GitHub
                                         div GitHub
-                                        small.text-muted Source code
+                                        small.text-muted(translate) Source code
 
 
                                 button.list-group-item.list-group-item-action.link-card((click)='showReleaseNotes()')
                                 button.list-group-item.list-group-item-action.link-card((click)='showReleaseNotes()')
                                     i.fas.fa-fw.fa-book
                                     i.fas.fa-fw.fa-book
                                     div
                                     div
-                                        div What's new
-                                        small.text-muted Show release notes
+                                        div(translate) What's new
+                                        small.text-muted(translate) Show release notes
 
 
                                 button.list-group-item.list-group-item-action.link-card((click)='homeBase.openTwitter()')
                                 button.list-group-item.list-group-item-action.link-card((click)='homeBase.openTwitter()')
                                     i.fab.fa-fw.fa-twitter
                                     i.fab.fa-fw.fa-twitter
                                     div
                                     div
-                                        div Subscribe to updates
-                                        small.text-muted Tabby news and updates on Twitter
+                                        div(translate) Subscribe to updates
+                                        small.text-muted(translate) Tabby news and updates on Twitter
 
 
 
 
-                    h3 Application settings
+                    h3(translate) Application settings
+                    .form-line
+                        .header
+                            .title(translate) Language
+                        select.form-control([(ngModel)]='config.store.language', (ngModelChange)='saveConfiguration()')
+                            option([value]='null', translate) Automatic
+                            option(
+                                [value]='lang.code',
+                                *ngFor='let lang of locale.allLanguages'
+                            ) {{lang.name|translate}}
+
                     .form-line(*ngIf='platform.isShellIntegrationSupported()')
                     .form-line(*ngIf='platform.isShellIntegrationSupported()')
                         .header
                         .header
-                            .title Shell integration
-                            .description Allows quickly opening a terminal in the selected folder
+                            .title(translate) Shell integration
+                            .description(translate) Allows quickly opening a terminal in the selected folder
                         toggle([ngModel]='isShellIntegrationInstalled', (ngModelChange)='toggleShellIntegration()')
                         toggle([ngModel]='isShellIntegrationInstalled', (ngModelChange)='toggleShellIntegration()')
 
 
                     .form-line(*ngIf='hostApp.platform !== Platform.Web')
                     .form-line(*ngIf='hostApp.platform !== Platform.Web')
                         .header
                         .header
-                            .title Enable analytics
-                            .description We're only tracking your Tabby and OS versions.
+                            .title(translate) Enable analytics
+                            .description(translate) We're only tracking your Tabby and OS versions.
                         toggle(
                         toggle(
                             [(ngModel)]='config.store.enableAnalytics',
                             [(ngModel)]='config.store.enableAnalytics',
                             (ngModelChange)='saveConfiguration(true)',
                             (ngModelChange)='saveConfiguration(true)',
@@ -83,23 +93,23 @@
 
 
                     .form-line(*ngIf='hostApp.platform !== Platform.Web')
                     .form-line(*ngIf='hostApp.platform !== Platform.Web')
                         .header
                         .header
-                            .title Automatic Updates
-                            .description Enable automatic installation of updates when they become available.
+                            .title(translate) Automatic Updates
+                            .description(translate) Enable automatic installation of updates when they become available.
                         toggle([(ngModel)]='config.store.enableAutomaticUpdates', (ngModelChange)='saveConfiguration()')
                         toggle([(ngModel)]='config.store.enableAutomaticUpdates', (ngModelChange)='saveConfiguration()')
 
 
                     .form-line(*ngIf='hostApp.platform !== Platform.Web')
                     .form-line(*ngIf='hostApp.platform !== Platform.Web')
                         .header
                         .header
-                            .title Debugging
+                            .title(translate) Debugging
 
 
                         button.btn.btn-secondary((click)='hostWindow.openDevTools()')
                         button.btn.btn-secondary((click)='hostWindow.openDevTools()')
                             i.fas.fa-bug
                             i.fas.fa-bug
-                            span Open DevTools
+                            span(translate) Open DevTools
 
 
         ng-container(*ngFor='let provider of settingsProviders')
         ng-container(*ngFor='let provider of settingsProviders')
             li(*ngIf='provider.prioritized', [ngbNavItem]='provider.id')
             li(*ngIf='provider.prioritized', [ngbNavItem]='provider.id')
                 a(ngbNavLink)
                 a(ngbNavLink)
                     i(class='fas fa-fw mr-2 fa-{{provider.icon}}')
                     i(class='fas fa-fw mr-2 fa-{{provider.icon}}')
-                    | {{provider.title}}
+                    span(translate) {{provider.title}}
                 ng-template(ngbNavContent)
                 ng-template(ngbNavContent)
                     settings-tab-body([provider]='provider')
                     settings-tab-body([provider]='provider')
 
 
@@ -109,24 +119,24 @@
             li(*ngIf='!provider.prioritized', [ngbNavItem]='provider.id')
             li(*ngIf='!provider.prioritized', [ngbNavItem]='provider.id')
                 a(ngbNavLink)
                 a(ngbNavLink)
                     i(class='fas fa-fw mr-2 fa-{{provider.icon || "puzzle-piece"}}')
                     i(class='fas fa-fw mr-2 fa-{{provider.icon || "puzzle-piece"}}')
-                    | {{provider.title}}
+                    span(translate) {{provider.title}}
                 ng-template(ngbNavContent)
                 ng-template(ngbNavContent)
                     settings-tab-body([provider]='provider')
                     settings-tab-body([provider]='provider')
 
 
         li(ngbNavItem='config-file')
         li(ngbNavItem='config-file')
             a(ngbNavLink)
             a(ngbNavLink)
                 i.fas.fa-fw.fa-code.mr-2
                 i.fas.fa-fw.fa-code.mr-2
-                | Config file
+                span(translate) Config file
             ng-template.test(ngbNavContent)
             ng-template.test(ngbNavContent)
                 .d-flex.flex-column.w-100.h-100
                 .d-flex.flex-column.w-100.h-100
                     .h-100.d-flex
                     .h-100.d-flex
                         .w-100.d-flex.flex-column
                         .w-100.d-flex.flex-column
-                            h3 Config file
+                            h3(translate) Config file
                             textarea.form-control.h-100(
                             textarea.form-control.h-100(
                                 [(ngModel)]='configFile'
                                 [(ngModel)]='configFile'
                             )
                             )
                         .w-100.d-flex.flex-column(*ngIf='showConfigDefaults')
                         .w-100.d-flex.flex-column(*ngIf='showConfigDefaults')
-                            h3 Defaults
+                            h3(translate) Defaults
                             textarea.form-control.h-100(
                             textarea.form-control.h-100(
                                 [(ngModel)]='configDefaults',
                                 [(ngModel)]='configDefaults',
                                 readonly
                                 readonly
@@ -134,20 +144,25 @@
                     .mt-3.d-flex
                     .mt-3.d-flex
                         button.btn.btn-primary((click)='saveConfigFile()', *ngIf='isConfigFileValid()')
                         button.btn.btn-primary((click)='saveConfigFile()', *ngIf='isConfigFileValid()')
                             i.fas.fa-check.mr-2
                             i.fas.fa-check.mr-2
-                            | Save and apply
+                            span(translate) Save and apply
                         button.btn.btn-primary(disabled, *ngIf='!isConfigFileValid()')
                         button.btn.btn-primary(disabled, *ngIf='!isConfigFileValid()')
                             i.fas.fa-exclamation-triangle.mr-2
                             i.fas.fa-exclamation-triangle.mr-2
-                            | Invalid syntax
+                            span(translate) Invalid syntax
                         button.btn.btn-secondary.ml-auto(
                         button.btn.btn-secondary.ml-auto(
-                            (click)='showConfigDefaults = !showConfigDefaults'
+                            (click)='showConfigDefaults = !showConfigDefaults',
+                            translate
                         ) Show defaults
                         ) Show defaults
                         button.btn.btn-secondary.ml-3(
                         button.btn.btn-secondary.ml-3(
                             *ngIf='platform.getConfigPath()',
                             *ngIf='platform.getConfigPath()',
                             (click)='showConfigFile()'
                             (click)='showConfigFile()'
                         )
                         )
                             i.fas.fa-external-link-square-alt.mr-2
                             i.fas.fa-external-link-square-alt.mr-2
-                            | Show config file
+                            span(translate) Show config file
 
 
     div([ngbNavOutlet]='nav')
     div([ngbNavOutlet]='nav')
 
 
-button.btn.btn-warning.btn-block(*ngIf='config.restartRequested', '(click)'='restartApp()') Restart the app to apply changes
+button.btn.btn-warning.btn-block(
+    *ngIf='config.restartRequested',
+    (click)='restartApp()',
+    translate
+) Restart the app to apply changes

+ 5 - 1
tabby-settings/src/components/settingsTab.component.ts

@@ -12,6 +12,8 @@ import {
     PlatformService,
     PlatformService,
     HostWindowService,
     HostWindowService,
     AppService,
     AppService,
+    LocaleService,
+    TranslateService,
 } from 'tabby-core'
 } from 'tabby-core'
 
 
 import { SettingsTabProvider } from '../api'
 import { SettingsTabProvider } from '../api'
@@ -43,12 +45,14 @@ export class SettingsTabComponent extends BaseTabComponent {
         public homeBase: HomeBaseService,
         public homeBase: HomeBaseService,
         public platform: PlatformService,
         public platform: PlatformService,
         public zone: NgZone,
         public zone: NgZone,
+        public locale: LocaleService,
         private updater: UpdaterService,
         private updater: UpdaterService,
         private app: AppService,
         private app: AppService,
         @Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
         @Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
+        translate: TranslateService,
     ) {
     ) {
         super()
         super()
-        this.setTitle('Settings')
+        this.setTitle(translate.instant('Settings'))
         this.settingsProviders = config.enabledServices(this.settingsProviders)
         this.settingsProviders = config.enabledServices(this.settingsProviders)
         this.settingsProviders = this.settingsProviders.filter(x => !!x.getComponentType())
         this.settingsProviders = this.settingsProviders.filter(x => !!x.getComponentType())
         this.settingsProviders.sort((a, b) => a.weight - b.weight + a.title.localeCompare(b.title))
         this.settingsProviders.sort((a, b) => a.weight - b.weight + a.title.localeCompare(b.title))

+ 17 - 17
tabby-settings/src/components/vaultSettingsTab.component.pug

@@ -1,27 +1,27 @@
 .text-center(*ngIf='!vault.isEnabled()')
 .text-center(*ngIf='!vault.isEnabled()')
     i.fas.fa-key.fa-3x.m-3
     i.fas.fa-key.fa-3x.m-3
-    h3.m-3 Vault is not configured
-    .m-3 Vault is an always-encrypted container for secrets such as SSH passwords and private key passphrases.
-    button.btn.btn-primary.m-2((click)='enableVault()') Set master passphrase
+    h3.m-3(translate) Vault is not configured
+    .m-3(translate) Vault is an always-encrypted container for secrets such as SSH passwords and private key passphrases.
+    button.btn.btn-primary.m-2((click)='enableVault()', translate) Set master passphrase
 
 
 
 
 div(*ngIf='vault.isEnabled()')
 div(*ngIf='vault.isEnabled()')
     .d-flex.align-items-center.mb-3
     .d-flex.align-items-center.mb-3
-        h3.m-0 Vault
+        h3.m-0(translate) Vault
         .d-flex.ml-auto(ngbDropdown, *ngIf='vault.isEnabled()')
         .d-flex.ml-auto(ngbDropdown, *ngIf='vault.isEnabled()')
-            button.btn.btn-secondary(ngbDropdownToggle) Options
+            button.btn.btn-secondary(ngbDropdownToggle, translate) Options
             div(ngbDropdownMenu)
             div(ngbDropdownMenu)
                 a(ngbDropdownItem, (click)='changePassphrase()')
                 a(ngbDropdownItem, (click)='changePassphrase()')
                     i.fas.fa-fw.fa-key
                     i.fas.fa-fw.fa-key
-                    span Change the master passphrase
+                    span(translate) Change the master passphrase
                 a(ngbDropdownItem, (click)='disableVault()')
                 a(ngbDropdownItem, (click)='disableVault()')
                     i.fas.fa-fw.fa-radiation-alt
                     i.fas.fa-fw.fa-radiation-alt
-                    span Erase the vault
+                    span(translate) Erase the Vault
 
 
     div(*ngIf='vaultContents')
     div(*ngIf='vaultContents')
         .text-center(*ngIf='!vaultContents.secrets.length')
         .text-center(*ngIf='!vaultContents.secrets.length')
             i.fas.fa-empty-set.fa-3x
             i.fas.fa-empty-set.fa-3x
-            h3.m-3 Vault is empty
+            h3.m-3(translate) Vault is empty
 
 
         .list-group
         .list-group
             .list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let secret of vaultContents.secrets')
             .list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let secret of vaultContents.secrets')
@@ -38,30 +38,30 @@ div(*ngIf='vault.isEnabled()')
                             (click)='renameFile(secret)'
                             (click)='renameFile(secret)'
                         )
                         )
                             i.fas.fa-fw.fa-pencil-alt
                             i.fas.fa-fw.fa-pencil-alt
-                            span Rename
+                            span(translate) Rename
                         button(
                         button(
                             ngbDropdownItem,
                             ngbDropdownItem,
                             *ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
                             *ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
                             (click)='replaceFileContent(secret)'
                             (click)='replaceFileContent(secret)'
                         )
                         )
                             i.fas.fa-fw.fa-file-import
                             i.fas.fa-fw.fa-file-import
-                            span Replace
+                            span(translate) Replace
                         button(
                         button(
                             ngbDropdownItem,
                             ngbDropdownItem,
                             *ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
                             *ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
                             (click)='exportFile(secret)'
                             (click)='exportFile(secret)'
                         )
                         )
                             i.fas.fa-fw.fa-file-export
                             i.fas.fa-fw.fa-file-export
-                            span Export
+                            span(translate) Export
                         button(ngbDropdownItem, (click)='removeSecret(secret)')
                         button(ngbDropdownItem, (click)='removeSecret(secret)')
                             i.fas.fa-fw.fa-trash
                             i.fas.fa-fw.fa-trash
-                            span Delete
+                            span(translate) Delete
 
 
-        h3.mt-5 Options
+        h3.mt-5(translate) Options
         .form-line
         .form-line
             .header
             .header
-                .title Encrypt config file
-                .description Puts all of Tabby's configuration into the vault
+                .title(translate) Encrypt config file
+                .description(translate) Puts all of Tabby's configuration into the vault
             toggle(
             toggle(
                 [ngModel]='config.store.encrypted',
                 [ngModel]='config.store.encrypted',
                 (click)='toggleConfigEncrypted()',
                 (click)='toggleConfigEncrypted()',
@@ -69,5 +69,5 @@ div(*ngIf='vault.isEnabled()')
 
 
     .text-center(*ngIf='!vaultContents')
     .text-center(*ngIf='!vaultContents')
         i.fas.fa-key.fa-3x
         i.fas.fa-key.fa-3x
-        h3.m-3 Vault is locked
-        button.btn.btn-primary.m-2((click)='loadVault()') Show vault contents
+        h3.m-3(translate) Vault is locked
+        button.btn.btn-primary.m-2((click)='loadVault()', translate) Show vault contents

+ 12 - 8
tabby-settings/src/components/vaultSettingsTab.component.ts

@@ -1,7 +1,7 @@
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 import { Component, HostBinding } from '@angular/core'
 import { Component, HostBinding } from '@angular/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
-import { BaseComponent, VaultService, VaultSecret, Vault, PlatformService, ConfigService, VAULT_SECRET_TYPE_FILE, PromptModalComponent, VaultFileSecret } from 'tabby-core'
+import { BaseComponent, VaultService, VaultSecret, Vault, PlatformService, ConfigService, VAULT_SECRET_TYPE_FILE, PromptModalComponent, VaultFileSecret, TranslateService } from 'tabby-core'
 import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.component'
 import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.component'
 
 
 
 
@@ -21,6 +21,7 @@ export class VaultSettingsTabComponent extends BaseComponent {
         public config: ConfigService,
         public config: ConfigService,
         private platform: PlatformService,
         private platform: PlatformService,
         private ngbModal: NgbModal,
         private ngbModal: NgbModal,
+        private translate: TranslateService,
     ) {
     ) {
         super()
         super()
         if (vault.isOpen()) {
         if (vault.isOpen()) {
@@ -43,8 +44,11 @@ export class VaultSettingsTabComponent extends BaseComponent {
         if ((await this.platform.showMessageBox(
         if ((await this.platform.showMessageBox(
             {
             {
                 type: 'warning',
                 type: 'warning',
-                message: 'Delete vault contents?',
-                buttons: ['Delete', 'Keep'],
+                message: this.translate.instant('Delete vault contents?'),
+                buttons: [
+                    this.translate.instant('Delete'),
+                    this.translate.instant('Keep'),
+                ],
                 defaultId: 1,
                 defaultId: 1,
                 cancelId: 1,
                 cancelId: 1,
             }
             }
@@ -77,16 +81,16 @@ export class VaultSettingsTabComponent extends BaseComponent {
 
 
     getSecretLabel (secret: VaultSecret) {
     getSecretLabel (secret: VaultSecret) {
         if (secret.type === 'ssh:password') {
         if (secret.type === 'ssh:password') {
-            return `SSH password for ${(secret as any).key.user}@${(secret as any).key.host}:${(secret as any).key.port}`
+            return this.translate.instant('SSH password for {user}@{host}:{port}', (secret as any).key)
         }
         }
         if (secret.type === 'ssh:key-passphrase') {
         if (secret.type === 'ssh:key-passphrase') {
-            return `Passphrase for a private key with hash ${(secret as any).key.hash.substring(0, 8)}...`
+            return this.translate.instant('Passphrase for a private key with hash {hash}...', { hash: (secret as any).key.hash.substring(0, 8) })
         }
         }
         if (secret.type === VAULT_SECRET_TYPE_FILE) {
         if (secret.type === VAULT_SECRET_TYPE_FILE) {
             // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
             // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
-            return `File: ${(secret as VaultFileSecret).key.description}`
+            return this.translate.instant('File: {description}', (secret as VaultFileSecret).key)
         }
         }
-        return `Unknown secret of type ${secret.type} for ${JSON.stringify(secret.key)}`
+        return this.translate.instant('Unknown secret of type {type} for {key}', { type: secret.type, key: JSON.stringify(secret.key) })
     }
     }
 
 
     removeSecret (secret: VaultSecret) {
     removeSecret (secret: VaultSecret) {
@@ -111,7 +115,7 @@ export class VaultSettingsTabComponent extends BaseComponent {
 
 
     async renameFile (secret: VaultFileSecret) {
     async renameFile (secret: VaultFileSecret) {
         const modal = this.ngbModal.open(PromptModalComponent)
         const modal = this.ngbModal.open(PromptModalComponent)
-        modal.componentInstance.prompt = 'New name'
+        modal.componentInstance.prompt = this.translate.instant('New name')
         modal.componentInstance.value = secret.key.description
         modal.componentInstance.value = secret.key.description
 
 
         const description = (await modal.result)?.value
         const description = (await modal.result)?.value

+ 46 - 46
tabby-settings/src/components/windowSettingsTab.component.pug

@@ -1,8 +1,8 @@
-h3.mb-3 Window
+h3.mb-3(translate) Window
 
 
 .form-line
 .form-line
     .header
     .header
-        .title Theme
+        .title(translate) Theme
     select.form-control(
     select.form-control(
         [(ngModel)]='config.store.appearance.theme',
         [(ngModel)]='config.store.appearance.theme',
         (ngModelChange)='saveConfiguration()',
         (ngModelChange)='saveConfiguration()',
@@ -12,8 +12,8 @@ h3.mb-3 Window
 
 
 .form-line(*ngIf='hostApp.platform === Platform.Web')
 .form-line(*ngIf='hostApp.platform === Platform.Web')
     .header
     .header
-        .title Ask before closing the browser tab
-        .description Prevents accidental closing
+        .title(translate) Ask before closing the browser tab
+        .description(translate) Prevents accidental closing
     toggle(
     toggle(
         [(ngModel)]='config.store.web.preventAccidentalTabClosure',
         [(ngModel)]='config.store.web.preventAccidentalTabClosure',
         (ngModelChange)='saveConfiguration()',
         (ngModelChange)='saveConfiguration()',
@@ -22,9 +22,9 @@ h3.mb-3 Window
 
 
 .form-line(*ngIf='platform.supportsWindowControls')
 .form-line(*ngIf='platform.supportsWindowControls')
     .header
     .header
-        .title(*ngIf='hostApp.platform !== Platform.macOS') Acrylic background
-        .title(*ngIf='hostApp.platform === Platform.macOS') Vibrancy
-        .description Gives the window a blurred transparent background
+        .title(*ngIf='hostApp.platform !== Platform.macOS', translate) Acrylic background
+        .title(*ngIf='hostApp.platform === Platform.macOS', translate) Vibrancy
+        .description(translate) Gives the window a blurred transparent background
 
 
     toggle(
     toggle(
         [(ngModel)]='config.store.appearance.vibrancy',
         [(ngModel)]='config.store.appearance.vibrancy',
@@ -33,7 +33,7 @@ h3.mb-3 Window
 
 
 .form-line(*ngIf='config.store.appearance.vibrancy && isFluentVibrancySupported')
 .form-line(*ngIf='config.store.appearance.vibrancy && isFluentVibrancySupported')
     .header
     .header
-        .title Background type
+        .title(translate) Background type
     .btn-group(
     .btn-group(
         [(ngModel)]='config.store.appearance.vibrancyType',
         [(ngModel)]='config.store.appearance.vibrancyType',
         (ngModelChange)='saveConfiguration()',
         (ngModelChange)='saveConfiguration()',
@@ -45,18 +45,18 @@ h3.mb-3 Window
                 ngbButton,
                 ngbButton,
                 [value]='"blur"'
                 [value]='"blur"'
             )
             )
-            | Blur
+            span(translate) Blur
         label.btn.btn-secondary(ngbButtonLabel)
         label.btn.btn-secondary(ngbButtonLabel)
             input(
             input(
                 type='radio',
                 type='radio',
                 ngbButton,
                 ngbButton,
                 [value]='"fluent"'
                 [value]='"fluent"'
             )
             )
-            | Fluent
+            span Fluent
 
 
 .form-line(*ngIf='platform.supportsWindowControls')
 .form-line(*ngIf='platform.supportsWindowControls')
     .header
     .header
-        .title Opacity
+        .title(translate) Opacity
     input(
     input(
         type='range',
         type='range',
         [(ngModel)]='config.store.appearance.opacity',
         [(ngModel)]='config.store.appearance.opacity',
@@ -68,8 +68,8 @@ h3.mb-3 Window
 
 
 .form-line(*ngIf='platform.supportsWindowControls')
 .form-line(*ngIf='platform.supportsWindowControls')
     .header
     .header
-        .title Window frame
-        .description Whether a custom window or an OS native window should be used
+        .title(translate) Window frame
+        .description(translate) Whether a custom window or an OS native window should be used
 
 
     .btn-group(
     .btn-group(
         [(ngModel)]='config.store.appearance.frame',
         [(ngModel)]='config.store.appearance.frame',
@@ -82,28 +82,28 @@ h3.mb-3 Window
                 ngbButton,
                 ngbButton,
                 [value]='"native"'
                 [value]='"native"'
             )
             )
-            | Native
+            span(translate) Native
         label.btn.btn-secondary(ngbButtonLabel)
         label.btn.btn-secondary(ngbButtonLabel)
             input(
             input(
                 type='radio',
                 type='radio',
                 ngbButton,
                 ngbButton,
                 [value]='"thin"'
                 [value]='"thin"'
             )
             )
-            | Thin
+            span(translate) Thin
         label.btn.btn-secondary(ngbButtonLabel)
         label.btn.btn-secondary(ngbButtonLabel)
             input(
             input(
                 type='radio',
                 type='radio',
                 ngbButton,
                 ngbButton,
                 [value]='"full"'
                 [value]='"full"'
             )
             )
-            | Full
+            span(translate) Full
 
 
-h3.mt-4 Docking
+h3.mt-4(translate) Docking
 
 
 .form-line(*ngIf='docking')
 .form-line(*ngIf='docking')
     .header
     .header
-        .title Dock the terminal
-        .description Snaps the window to a side of the screen
+        .title(translate) Dock the terminal
+        .description(translate) Snaps the window to a side of the screen
 
 
     .btn-group(
     .btn-group(
         [(ngModel)]='config.store.appearance.dock',
         [(ngModel)]='config.store.appearance.dock',
@@ -116,40 +116,40 @@ h3.mt-4 Docking
                 ngbButton,
                 ngbButton,
                 [value]='"off"'
                 [value]='"off"'
             )
             )
-            | Off
+            span(translate) Off
         label.btn.btn-secondary(ngbButtonLabel)
         label.btn.btn-secondary(ngbButtonLabel)
             input(
             input(
                 type='radio',
                 type='radio',
                 ngbButton,
                 ngbButton,
                 [value]='"top"'
                 [value]='"top"'
             )
             )
-            | Top
+            span(translate) Top
         label.btn.btn-secondary(ngbButtonLabel)
         label.btn.btn-secondary(ngbButtonLabel)
             input(
             input(
                 type='radio',
                 type='radio',
                 ngbButton,
                 ngbButton,
                 [value]='"left"'
                 [value]='"left"'
             )
             )
-            | Left
+            span(translate) Left
         label.btn.btn-secondary(ngbButtonLabel)
         label.btn.btn-secondary(ngbButtonLabel)
             input(
             input(
                 type='radio',
                 type='radio',
                 ngbButton,
                 ngbButton,
                 [value]='"right"'
                 [value]='"right"'
             )
             )
-            | Right
+            span(translate) Right
         label.btn.btn-secondary(ngbButtonLabel)
         label.btn.btn-secondary(ngbButtonLabel)
             input(
             input(
                 type='radio',
                 type='radio',
                 ngbButton,
                 ngbButton,
                 [value]='"bottom"'
                 [value]='"bottom"'
             )
             )
-            | Bottom
+            span(translate) Bottom
 
 
 .ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
 .ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
     .header
     .header
-        .title Display on
-        .description Snaps the window to a side of the screen
+        .title(translate) Display on
+        .description(translate) Snaps the window to a side of the screen
 
 
     div(
     div(
         [(ngModel)]='config.store.appearance.dockScreen',
         [(ngModel)]='config.store.appearance.dockScreen',
@@ -162,7 +162,7 @@ h3.mt-4 Docking
                 ngbButton,
                 ngbButton,
                 value='current'
                 value='current'
             )
             )
-            | Current
+            span(translate) Current
         label.btn.btn-secondary(*ngFor='let screen of screens', ngbButtonLabel)
         label.btn.btn-secondary(*ngFor='let screen of screens', ngbButtonLabel)
             input(
             input(
                 type='radio',
                 type='radio',
@@ -173,8 +173,8 @@ h3.mt-4 Docking
 
 
 .ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
 .ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
     .header
     .header
-        .title Dock always on top
-        .description Keep docked terminal always on top
+        .title(translate) Dock always on top
+        .description(translate) Keep docked terminal always on top
     toggle(
     toggle(
         [(ngModel)]='config.store.appearance.dockAlwaysOnTop',
         [(ngModel)]='config.store.appearance.dockAlwaysOnTop',
         (ngModelChange)='saveConfiguration(); docking.dock()',
         (ngModelChange)='saveConfiguration(); docking.dock()',
@@ -182,7 +182,7 @@ h3.mt-4 Docking
 
 
 .ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
 .ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
     .header
     .header
-        .title Docked terminal size
+        .title(translate) Docked terminal size
     input(
     input(
         type='range',
         type='range',
         [(ngModel)]='config.store.appearance.dockFill',
         [(ngModel)]='config.store.appearance.dockFill',
@@ -194,7 +194,7 @@ h3.mt-4 Docking
 
 
 .ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
 .ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
     .header
     .header
-        .title Docked terminal space
+        .title(translate) Docked terminal space
     input(
     input(
         type='range',
         type='range',
         [(ngModel)]='config.store.appearance.dockSpace',
         [(ngModel)]='config.store.appearance.dockSpace',
@@ -206,18 +206,18 @@ h3.mt-4 Docking
 
 
 .ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
 .ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
     .header
     .header
-        .title Hide dock on blur
-        .description Hides the docked terminal when you click away.
+        .title(translate) Hide dock on blur
+        .description(translate) Hides the docked terminal when you click away.
     toggle(
     toggle(
         [(ngModel)]='config.store.appearance.dockHideOnBlur',
         [(ngModel)]='config.store.appearance.dockHideOnBlur',
         (ngModelChange)='saveConfiguration(); ',
         (ngModelChange)='saveConfiguration(); ',
     )
     )
 
 
-h3.mt-4 Tabs
+h3.mt-4(translate) Tabs
 
 
 .form-line
 .form-line
     .header
     .header
-        .title Tabs location
+        .title(translate) Tabs location
     .btn-group(
     .btn-group(
         [(ngModel)]='config.store.appearance.tabsLocation',
         [(ngModel)]='config.store.appearance.tabsLocation',
         (ngModelChange)='saveConfiguration()',
         (ngModelChange)='saveConfiguration()',
@@ -229,32 +229,32 @@ h3.mt-4 Tabs
                 ngbButton,
                 ngbButton,
                 [value]='"top"'
                 [value]='"top"'
             )
             )
-            | Top
+            span(translate) Top
         label.btn.btn-secondary(ngbButtonLabel)
         label.btn.btn-secondary(ngbButtonLabel)
             input(
             input(
                 type='radio',
                 type='radio',
                 ngbButton,
                 ngbButton,
                 [value]='"bottom"'
                 [value]='"bottom"'
             )
             )
-            | Bottom
+            span(translate) Bottom
         label.btn.btn-secondary(ngbButtonLabel)
         label.btn.btn-secondary(ngbButtonLabel)
             input(
             input(
                 type='radio',
                 type='radio',
                 ngbButton,
                 ngbButton,
                 [value]='"left"'
                 [value]='"left"'
             )
             )
-            | Left
+            span(translate) Left
         label.btn.btn-secondary(ngbButtonLabel)
         label.btn.btn-secondary(ngbButtonLabel)
             input(
             input(
                 type='radio',
                 type='radio',
                 ngbButton,
                 ngbButton,
                 [value]='"right"'
                 [value]='"right"'
             )
             )
-            | Right
+            span(translate) Right
 
 
 .form-line
 .form-line
     .header
     .header
-        .title Tabs width
+        .title(translate) Tabs width
     .btn-group(
     .btn-group(
         [(ngModel)]='config.store.appearance.flexTabs',
         [(ngModel)]='config.store.appearance.flexTabs',
         (ngModelChange)='saveConfiguration()',
         (ngModelChange)='saveConfiguration()',
@@ -266,18 +266,18 @@ h3.mt-4 Tabs
                 ngbButton,
                 ngbButton,
                 [value]='true'
                 [value]='true'
             )
             )
-            | Dynamic
+            span(translate) Dynamic
         label.btn.btn-secondary(ngbButtonLabel)
         label.btn.btn-secondary(ngbButtonLabel)
             input(
             input(
                 type='radio',
                 type='radio',
                 ngbButton,
                 ngbButton,
                 [value]='false'
                 [value]='false'
             )
             )
-            | Fixed
+            span(translate) Fixed
 
 
 .form-line
 .form-line
     .header
     .header
-        .title Hide tab index
+        .title(translate) Hide tab index
 
 
     toggle(
     toggle(
         [(ngModel)]='config.store.terminal.hideTabIndex',
         [(ngModel)]='config.store.terminal.hideTabIndex',
@@ -286,7 +286,7 @@ h3.mt-4 Tabs
 
 
 .form-line
 .form-line
     .header
     .header
-        .title Hide tab close button
+        .title(translate) Hide tab close button
 
 
     toggle(
     toggle(
         [(ngModel)]='config.store.terminal.hideCloseButton',
         [(ngModel)]='config.store.terminal.hideCloseButton',
@@ -297,8 +297,8 @@ h3.mt-4 Hacks
 
 
 .form-line
 .form-line
     .header
     .header
-        .title Disable GPU acceleration
-        .description Tick this if you're experiencing aliasing, ghosting or other visual issues
+        .title(translate) Disable GPU acceleration
+        .description(translate) Tick this if you're experiencing aliasing, ghosting or other visual issues
 
 
     toggle(
     toggle(
         [(ngModel)]='config.store.hacks.disableGPU',
         [(ngModel)]='config.store.hacks.disableGPU',

+ 4 - 2
tabby-settings/src/hotkeys.ts

@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core'
 import { Injectable } from '@angular/core'
-import { HotkeyDescription, HotkeyProvider } from 'tabby-core'
+import { HotkeyDescription, HotkeyProvider, TranslateService } from 'tabby-core'
 
 
 /** @hidden */
 /** @hidden */
 @Injectable()
 @Injectable()
@@ -7,10 +7,12 @@ export class SettingsHotkeyProvider extends HotkeyProvider {
     hotkeys: HotkeyDescription[] = [
     hotkeys: HotkeyDescription[] = [
         {
         {
             id: 'settings',
             id: 'settings',
-            name: 'Open Settings',
+            name: this.translate.instant('Open Settings'),
         },
         },
     ]
     ]
 
 
+    constructor (private translate: TranslateService) { super() }
+
     async provide (): Promise<HotkeyDescription[]> {
     async provide (): Promise<HotkeyDescription[]> {
         return this.hotkeys
         return this.hotkeys
     }
     }

+ 13 - 4
tabby-settings/src/settings.ts

@@ -5,13 +5,16 @@ import { WindowSettingsTabComponent } from './components/windowSettingsTab.compo
 import { VaultSettingsTabComponent } from './components/vaultSettingsTab.component'
 import { VaultSettingsTabComponent } from './components/vaultSettingsTab.component'
 import { ConfigSyncSettingsTabComponent } from './components/configSyncSettingsTab.component'
 import { ConfigSyncSettingsTabComponent } from './components/configSyncSettingsTab.component'
 import { ProfilesSettingsTabComponent } from './components/profilesSettingsTab.component'
 import { ProfilesSettingsTabComponent } from './components/profilesSettingsTab.component'
+import { TranslateService } from 'tabby-core'
 
 
 /** @hidden */
 /** @hidden */
 @Injectable()
 @Injectable()
 export class HotkeySettingsTabProvider extends SettingsTabProvider {
 export class HotkeySettingsTabProvider extends SettingsTabProvider {
     id = 'hotkeys'
     id = 'hotkeys'
     icon = 'keyboard'
     icon = 'keyboard'
-    title = 'Hotkeys'
+    title = this.translate.instant('Hotkeys')
+
+    constructor (private translate: TranslateService) { super() }
 
 
     getComponentType (): any {
     getComponentType (): any {
         return HotkeySettingsTabComponent
         return HotkeySettingsTabComponent
@@ -24,7 +27,9 @@ export class HotkeySettingsTabProvider extends SettingsTabProvider {
 export class WindowSettingsTabProvider extends SettingsTabProvider {
 export class WindowSettingsTabProvider extends SettingsTabProvider {
     id = 'window'
     id = 'window'
     icon = 'window-maximize'
     icon = 'window-maximize'
-    title = 'Window'
+    title = this.translate.instant('Window')
+
+    constructor (private translate: TranslateService) { super() }
 
 
     getComponentType (): any {
     getComponentType (): any {
         return WindowSettingsTabComponent
         return WindowSettingsTabComponent
@@ -50,9 +55,11 @@ export class VaultSettingsTabProvider extends SettingsTabProvider {
 export class ProfilesSettingsTabProvider extends SettingsTabProvider {
 export class ProfilesSettingsTabProvider extends SettingsTabProvider {
     id = 'profiles'
     id = 'profiles'
     icon = 'window-restore'
     icon = 'window-restore'
-    title = 'Profiles & connections'
+    title = this.translate.instant('Profiles & connections')
     prioritized = true
     prioritized = true
 
 
+    constructor (private translate: TranslateService) { super() }
+
     getComponentType (): any {
     getComponentType (): any {
         return ProfilesSettingsTabComponent
         return ProfilesSettingsTabComponent
     }
     }
@@ -63,7 +70,9 @@ export class ProfilesSettingsTabProvider extends SettingsTabProvider {
 export class ConfigSyncSettingsTabProvider extends SettingsTabProvider {
 export class ConfigSyncSettingsTabProvider extends SettingsTabProvider {
     id = 'config-sync'
     id = 'config-sync'
     icon = 'cloud'
     icon = 'cloud'
-    title = 'Config sync'
+    title = this.translate.instant('Config sync')
+
+    constructor (private translate: TranslateService) { super() }
 
 
     getComponentType (): any {
     getComponentType (): any {
         return ConfigSyncSettingsTabComponent
         return ConfigSyncSettingsTabComponent

+ 1 - 1
tabby-ssh/src/components/keyboardInteractiveAuthPanel.component.pug

@@ -1,5 +1,5 @@
 .d-flex
 .d-flex
-    strong Keyboard-interactive auth
+    strong(translate) Keyboard-interactive auth
     .ml-2 {{prompt.name}}
     .ml-2 {{prompt.name}}
 
 
 .prompt-text {{prompt.prompts[step].prompt}}
 .prompt-text {{prompt.prompts[step].prompt}}

+ 2 - 2
tabby-ssh/src/components/sftpDeleteModal.component.pug

@@ -1,6 +1,6 @@
 .modal-body
 .modal-body
-    label Deleting
+    label(translate) Deleting
     .no-wrap {{progressMessage}}
     .no-wrap {{progressMessage}}
 
 
 .modal-footer
 .modal-footer
-    button.btn.btn-danger((click)='cancel()') Cancel
+    button.btn.btn-danger((click)='cancel()', translate) Cancel

+ 4 - 4
tabby-ssh/src/components/sftpPanel.component.pug

@@ -8,21 +8,21 @@
 
 
     button.btn.btn-link.btn-sm.d-flex((click)='upload()')
     button.btn.btn-link.btn-sm.d-flex((click)='upload()')
         i.fas.fa-upload.mr-1
         i.fas.fa-upload.mr-1
-        div Upload
+        div(translate) Upload
 
 
     button.btn.btn-link.btn-close((click)='close()') !{require('../../../tabby-core/src/icons/times.svg')}
     button.btn.btn-link.btn-close((click)='close()') !{require('../../../tabby-core/src/icons/times.svg')}
 
 
 .body(dropZone, (transfer)='uploadOne($event)')
 .body(dropZone, (transfer)='uploadOne($event)')
-    div(*ngIf='!sftp') Connecting
+    div(*ngIf='!sftp', translate) Connecting
     div(*ngIf='sftp')
     div(*ngIf='sftp')
-        div(*ngIf='fileList === null') Loading
+        div(*ngIf='fileList === null', translate) Loading
         .list-group.list-group-light(*ngIf='fileList !== null')
         .list-group.list-group-light(*ngIf='fileList !== null')
             .list-group-item.list-group-item-action.d-flex.align-items-center(
             .list-group-item.list-group-item-action.d-flex.align-items-center(
                 *ngIf='path !== "/"',
                 *ngIf='path !== "/"',
                 (click)='goUp()'
                 (click)='goUp()'
             )
             )
                 i.fas.fa-fw.fa-level-up-alt
                 i.fas.fa-fw.fa-level-up-alt
-                div Go up
+                div(translate) Go up
             .list-group-item.list-group-item-action.d-flex.align-items-center(
             .list-group-item.list-group-item-action.d-flex.align-items-center(
                 *ngFor='let item of fileList',
                 *ngFor='let item of fileList',
                 (contextmenu)='showContextMenu(item, $event)',
                 (contextmenu)='showContextMenu(item, $event)',

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini