Translation infrastructure

This commit is contained in:
Eugene Pankov
2022-01-08 16:02:56 +01:00
parent 04010b58bb
commit 0814d44207
134 changed files with 8137 additions and 889 deletions

View File

@@ -1,5 +1,5 @@
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'
@@ -10,6 +10,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
hostApp: HostAppService,
hotkeys: HotkeysService,
private app: AppService,
private translate: TranslateService,
) {
super()
hostApp.settingsUIRequest$.subscribe(() => this.open())
@@ -24,7 +25,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
provide (): ToolbarButton[] {
return [{
icon: require('./icons/cog.svg'),
title: 'Settings',
title: this.translate.instant('Settings'),
touchBarNSImage: 'NSTouchBarComposeTemplate',
weight: 10,
click: (): void => this.open(),

View File

@@ -1,12 +1,12 @@
h3.mb-3 Config sync
h3.mb-3(translate) Config sync
ul.nav-tabs(ngbNav, #nav='ngbNav')
li(ngbNavItem)
a(ngbNavLink) Sync
a(ngbNavLink, translate) Sync
ng-template(ngbNavContent)
.form-line
.header
.title Sync host
.title(translate) Sync host
.input-group.w-50
input.form-control(
@@ -20,8 +20,8 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
.form-line
.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.form-control(
@@ -38,16 +38,16 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
ng-container(*ngIf='config.store.configSync.token')
.alert.alert-danger(*ngIf='connectionSuccessful === false')
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')
.form-line
.header
.title Configs
.title(translate) Configs
div(*ngIf='configs === null')
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')
.list-group-light
@@ -59,34 +59,34 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
i.fas.fa-fw.fa-file
.ml-2.d-flex.flex-column.align-items-start
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
button.btn.btn-link.ml-1(
(click)='uploadAndSync(cfg)',
[class.hover-reveal]='!isActiveConfig(cfg)'
)
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(
(click)='downloadAndSync(cfg)',
[class.hover-reveal]='!isActiveConfig(cfg)'
)
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(
href='#',
(click)='uploadAsNew()'
)
i.fas.fa-fw
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()')
.form-line
.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(
[(ngModel)]='config.store.configSync.auto',
@@ -94,11 +94,11 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
)
li(ngbNavItem)
a(ngbNavLink) Advanced
a(ngbNavLink, translate) Advanced
ng-template(ngbNavContent)
.form-line
.header
.title Sync hotkeys
.title(translate) Sync hotkeys
toggle(
[(ngModel)]='config.store.configSync.parts.hotkeys',
(ngModelChange)='config.save()',
@@ -106,7 +106,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
.form-line
.header
.title Sync window settings
.title(translate) Sync window settings
toggle(
[(ngModel)]='config.store.configSync.parts.appearance',
(ngModelChange)='config.save()',
@@ -114,7 +114,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
.form-line
.header
.title Sync Vault
.title(translate) Sync Vault
toggle(
[(ngModel)]='config.store.configSync.parts.vault',
(ngModelChange)='config.save()',

View File

@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component, HostBinding } from '@angular/core'
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'
@@ -24,6 +24,7 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
private hostApp: HostAppService,
private ngbModal: NgbModal,
private notifications: NotificationsService,
private translate: TranslateService,
) {
super()
}
@@ -54,9 +55,9 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
}
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)
modal.componentInstance.prompt = 'Name for the new config'
modal.componentInstance.prompt = this.translate.instant('Name for the new config')
modal.componentInstance.value = name
name = (await modal.result)?.value
if (!name) {
@@ -72,8 +73,11 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
if (this.config.store.configSync.configID !== cfg.id) {
if ((await this.platform.showMessageBox({
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,
cancelId: 1,
})).response === 1) {
@@ -83,14 +87,17 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
this.configSync.setConfig(cfg)
await this.configSync.upload()
this.loadConfigs()
this.notifications.info('Config uploaded')
this.notifications.info(this.translate.instant('Config uploaded'))
}
async downloadAndSync (cfg: Config) {
if ((await this.platform.showMessageBox({
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,
cancelId: 1,
})).response === 1) {
@@ -98,7 +105,7 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent {
}
this.configSync.setConfig(cfg)
await this.configSync.download()
this.notifications.info('Config downloaded')
this.notifications.info(this.translate.instant('Config downloaded'))
}
hasMatchingRemoteConfig () {

View File

@@ -2,13 +2,13 @@
h3.m-0 {{profile.name}}
.modal-header(*ngIf='defaultsMode')
h3.m-0 Defaults for {{profileProvider.name}}
h3.m-0(translate, [translateParams]='{type: profileProvider.name}') Defaults for {type}
.modal-body
.row
.col-12.col-lg-4
.form-group(*ngIf='!defaultsMode')
label Name
label(translate) Name
input.form-control(
type='text',
autofocus,
@@ -16,7 +16,7 @@
)
.form-group(*ngIf='!defaultsMode')
label Group
label(translate) Group
input.form-control(
type='text',
alwaysVisibleTypeahead,
@@ -26,7 +26,7 @@
)
.form-group(*ngIf='!defaultsMode')
label Icon
label(translate) Icon
.input-group
input.form-control(
type='text',
@@ -45,7 +45,7 @@
.form-line
.header
.title Color
.title(translate) Color
input.form-control.w-50(
type='text',
[(ngModel)]='profile.color',
@@ -56,8 +56,8 @@
.form-line
.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')
.mb-4
@@ -66,5 +66,5 @@
ng-template(#placeholder)
.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

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
h3.mb-3 Profiles
h3.mb-3(translate) Profiles
ul.nav-tabs(ngbNav, #nav='ngbNav')
li(ngbNavItem)
a(ngbNavLink) Profiles
a(ngbNavLink, translate) Profiles
ng-template(ngbNavContent)
.form-line
.header
.title Default profile for new tabs
.title(translate) Default profile for new tabs
select.form-control(
[(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()')
i.fas.fa-fw.fa-plus
| New profile
span(translate) New profile
.list-group.mt-3.mb-3
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-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(
*ngIf='group.editable && group.name',
(click)='$event.stopPropagation(); editGroup(group)'
@@ -88,12 +88,12 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
.ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}}
li(ngbNavItem)
a(ngbNavLink) Advanced
a(ngbNavLink, translate) Advanced
ng-template(ngbNavContent)
.form-line(*ngIf='config.store.profiles.length > 0')
.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(
type='number',
@@ -105,8 +105,8 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
.form-line(*ngIf='config.store.profiles.length > 0')
.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(
[(ngModel)]='config.store.terminal.showBuiltinProfiles',
@@ -115,8 +115,8 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
.form-line
.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
a.list-group-item.list-group-item-action(

View File

@@ -3,7 +3,7 @@ import slugify from 'slugify'
import deepClone from 'clone-deep'
import { Component, HostBinding, Inject } from '@angular/core'
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'
interface ProfileGroup {
@@ -35,6 +35,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
private selector: SelectorService,
private ngbModal: NgbModal,
private platform: PlatformService,
private translate: TranslateService,
) {
super()
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]
profiles.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0))
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 => ({
icon: p.icon,
description: this.profilesService.getDescription(p) ?? undefined,
@@ -72,14 +73,14 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
if (base.isTemplate) {
profile.name = ''
} else if (!base.isBuiltin) {
profile.name = `${base.name} copy`
profile.name = this.translate.instant('{name} copy', base)
}
profile.isBuiltin = false
profile.isTemplate = false
await this.showProfileEditModal(profile)
if (!profile.name) {
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()}`
this.config.store.profiles = [profile, ...this.config.store.profiles]
@@ -122,8 +123,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
if ((await this.platform.showMessageBox(
{
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,
cancelId: 1,
}
@@ -156,7 +160,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
this.profileGroups.sort((a, b) => a.name?.localeCompare(b.name ?? '') ?? -1)
this.profileGroups.push({
name: 'Built-in',
name: this.translate.instant('Built-in'),
profiles: this.builtinProfiles,
editable: false,
collapsed: false,
@@ -165,7 +169,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
async editGroup (group: ProfileGroup): Promise<void> {
const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = 'New name'
modal.componentInstance.prompt = this.translate.instant('New name')
modal.componentInstance.value = group.name
const result = await modal.result
if (result) {
@@ -181,8 +185,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
if ((await this.platform.showMessageBox(
{
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,
cancelId: 1,
}
@@ -190,8 +197,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
if ((await this.platform.showMessageBox(
{
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,
cancelId: 0,
}
@@ -224,10 +234,10 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
getTypeLabel (profile: PartialProfile<Profile>): string {
const name = this.profilesService.providerForProfile(profile)?.name
if (name === 'Local') {
if (name === this.translate.instant('Local terminal')) {
return ''
}
return name ?? 'Unknown'
return name ?? this.translate.instant('Unknown')
}
getTypeColorClass (profile: PartialProfile<Profile>): string {

View File

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

View File

@@ -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
.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.form-control.form-control-lg(
[type]='showPassphrase ? "text" : "password"',
@@ -16,5 +16,5 @@ h3.modal-header.m-0.pb-0 Set master passphrase
i.fas.fa-eye
.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

View File

@@ -3,7 +3,7 @@
li(ngbNavItem='application')
a(ngbNavLink)
i.fas.fa-fw.fa-window-maximize.mr-2
| Application
span(translate) Application
ng-template(ngbNavContent)
.content-box
.row
@@ -23,59 +23,69 @@
i.fas.fa-sync(
[class.fa-spin]='checkingForUpdate'
)
span Check for updates
span(translate) Check for updates
button.btn.btn-info.mt-3.mb-2(
*ngIf='updateAvailable',
(click)='updater.update()',
)
i.fas.fa-sync
span Update
span(translate) Update
.col-12.col-md-6
.list-group.list-group-light.mb-5
button.list-group-item.list-group-item-action.link-card((click)='homeBase.reportBug()')
i.fas.fa-fw.fa-bug
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()')
i.fas.fa-fw.fa-comments
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()')
i.fab.fa-fw.fa-github
div
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()')
i.fas.fa-fw.fa-book
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()')
i.fab.fa-fw.fa-twitter
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()')
.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()')
.form-line(*ngIf='hostApp.platform !== Platform.Web')
.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(
[(ngModel)]='config.store.enableAnalytics',
(ngModelChange)='saveConfiguration(true)',
@@ -83,23 +93,23 @@
.form-line(*ngIf='hostApp.platform !== Platform.Web')
.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()')
.form-line(*ngIf='hostApp.platform !== Platform.Web')
.header
.title Debugging
.title(translate) Debugging
button.btn.btn-secondary((click)='hostWindow.openDevTools()')
i.fas.fa-bug
span Open DevTools
span(translate) Open DevTools
ng-container(*ngFor='let provider of settingsProviders')
li(*ngIf='provider.prioritized', [ngbNavItem]='provider.id')
a(ngbNavLink)
i(class='fas fa-fw mr-2 fa-{{provider.icon}}')
| {{provider.title}}
span(translate) {{provider.title}}
ng-template(ngbNavContent)
settings-tab-body([provider]='provider')
@@ -109,24 +119,24 @@
li(*ngIf='!provider.prioritized', [ngbNavItem]='provider.id')
a(ngbNavLink)
i(class='fas fa-fw mr-2 fa-{{provider.icon || "puzzle-piece"}}')
| {{provider.title}}
span(translate) {{provider.title}}
ng-template(ngbNavContent)
settings-tab-body([provider]='provider')
li(ngbNavItem='config-file')
a(ngbNavLink)
i.fas.fa-fw.fa-code.mr-2
| Config file
span(translate) Config file
ng-template.test(ngbNavContent)
.d-flex.flex-column.w-100.h-100
.h-100.d-flex
.w-100.d-flex.flex-column
h3 Config file
h3(translate) Config file
textarea.form-control.h-100(
[(ngModel)]='configFile'
)
.w-100.d-flex.flex-column(*ngIf='showConfigDefaults')
h3 Defaults
h3(translate) Defaults
textarea.form-control.h-100(
[(ngModel)]='configDefaults',
readonly
@@ -134,20 +144,25 @@
.mt-3.d-flex
button.btn.btn-primary((click)='saveConfigFile()', *ngIf='isConfigFileValid()')
i.fas.fa-check.mr-2
| Save and apply
span(translate) Save and apply
button.btn.btn-primary(disabled, *ngIf='!isConfigFileValid()')
i.fas.fa-exclamation-triangle.mr-2
| Invalid syntax
span(translate) Invalid syntax
button.btn.btn-secondary.ml-auto(
(click)='showConfigDefaults = !showConfigDefaults'
(click)='showConfigDefaults = !showConfigDefaults',
translate
) Show defaults
button.btn.btn-secondary.ml-3(
*ngIf='platform.getConfigPath()',
(click)='showConfigFile()'
)
i.fas.fa-external-link-square-alt.mr-2
| Show config file
span(translate) Show config file
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

View File

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

View File

@@ -1,27 +1,27 @@
.text-center(*ngIf='!vault.isEnabled()')
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()')
.d-flex.align-items-center.mb-3
h3.m-0 Vault
h3.m-0(translate) Vault
.d-flex.ml-auto(ngbDropdown, *ngIf='vault.isEnabled()')
button.btn.btn-secondary(ngbDropdownToggle) Options
button.btn.btn-secondary(ngbDropdownToggle, translate) Options
div(ngbDropdownMenu)
a(ngbDropdownItem, (click)='changePassphrase()')
i.fas.fa-fw.fa-key
span Change the master passphrase
span(translate) Change the master passphrase
a(ngbDropdownItem, (click)='disableVault()')
i.fas.fa-fw.fa-radiation-alt
span Erase the vault
span(translate) Erase the Vault
div(*ngIf='vaultContents')
.text-center(*ngIf='!vaultContents.secrets.length')
i.fas.fa-empty-set.fa-3x
h3.m-3 Vault is empty
h3.m-3(translate) Vault is empty
.list-group
.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)'
)
i.fas.fa-fw.fa-pencil-alt
span Rename
span(translate) Rename
button(
ngbDropdownItem,
*ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
(click)='replaceFileContent(secret)'
)
i.fas.fa-fw.fa-file-import
span Replace
span(translate) Replace
button(
ngbDropdownItem,
*ngIf='secret.type === VAULT_SECRET_TYPE_FILE',
(click)='exportFile(secret)'
)
i.fas.fa-fw.fa-file-export
span Export
span(translate) Export
button(ngbDropdownItem, (click)='removeSecret(secret)')
i.fas.fa-fw.fa-trash
span Delete
span(translate) Delete
h3.mt-5 Options
h3.mt-5(translate) Options
.form-line
.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(
[ngModel]='config.store.encrypted',
(click)='toggleConfigEncrypted()',
@@ -69,5 +69,5 @@ div(*ngIf='vault.isEnabled()')
.text-center(*ngIf='!vaultContents')
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

View File

@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component, HostBinding } from '@angular/core'
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'
@@ -21,6 +21,7 @@ export class VaultSettingsTabComponent extends BaseComponent {
public config: ConfigService,
private platform: PlatformService,
private ngbModal: NgbModal,
private translate: TranslateService,
) {
super()
if (vault.isOpen()) {
@@ -43,8 +44,11 @@ export class VaultSettingsTabComponent extends BaseComponent {
if ((await this.platform.showMessageBox(
{
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,
cancelId: 1,
}
@@ -77,16 +81,16 @@ export class VaultSettingsTabComponent extends BaseComponent {
getSecretLabel (secret: VaultSecret) {
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') {
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) {
// 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) {
@@ -111,7 +115,7 @@ export class VaultSettingsTabComponent extends BaseComponent {
async renameFile (secret: VaultFileSecret) {
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
const description = (await modal.result)?.value

View File

@@ -1,8 +1,8 @@
h3.mb-3 Window
h3.mb-3(translate) Window
.form-line
.header
.title Theme
.title(translate) Theme
select.form-control(
[(ngModel)]='config.store.appearance.theme',
(ngModelChange)='saveConfiguration()',
@@ -12,8 +12,8 @@ h3.mb-3 Window
.form-line(*ngIf='hostApp.platform === Platform.Web')
.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(
[(ngModel)]='config.store.web.preventAccidentalTabClosure',
(ngModelChange)='saveConfiguration()',
@@ -22,9 +22,9 @@ h3.mb-3 Window
.form-line(*ngIf='platform.supportsWindowControls')
.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(
[(ngModel)]='config.store.appearance.vibrancy',
@@ -33,7 +33,7 @@ h3.mb-3 Window
.form-line(*ngIf='config.store.appearance.vibrancy && isFluentVibrancySupported')
.header
.title Background type
.title(translate) Background type
.btn-group(
[(ngModel)]='config.store.appearance.vibrancyType',
(ngModelChange)='saveConfiguration()',
@@ -45,18 +45,18 @@ h3.mb-3 Window
ngbButton,
[value]='"blur"'
)
| Blur
span(translate) Blur
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"fluent"'
)
| Fluent
span Fluent
.form-line(*ngIf='platform.supportsWindowControls')
.header
.title Opacity
.title(translate) Opacity
input(
type='range',
[(ngModel)]='config.store.appearance.opacity',
@@ -68,8 +68,8 @@ h3.mb-3 Window
.form-line(*ngIf='platform.supportsWindowControls')
.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(
[(ngModel)]='config.store.appearance.frame',
@@ -82,28 +82,28 @@ h3.mb-3 Window
ngbButton,
[value]='"native"'
)
| Native
span(translate) Native
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"thin"'
)
| Thin
span(translate) Thin
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"full"'
)
| Full
span(translate) Full
h3.mt-4 Docking
h3.mt-4(translate) Docking
.form-line(*ngIf='docking')
.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(
[(ngModel)]='config.store.appearance.dock',
@@ -116,40 +116,40 @@ h3.mt-4 Docking
ngbButton,
[value]='"off"'
)
| Off
span(translate) Off
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"top"'
)
| Top
span(translate) Top
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"left"'
)
| Left
span(translate) Left
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"right"'
)
| Right
span(translate) Right
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"bottom"'
)
| Bottom
span(translate) Bottom
.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
.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(
[(ngModel)]='config.store.appearance.dockScreen',
@@ -162,7 +162,7 @@ h3.mt-4 Docking
ngbButton,
value='current'
)
| Current
span(translate) Current
label.btn.btn-secondary(*ngFor='let screen of screens', ngbButtonLabel)
input(
type='radio',
@@ -173,8 +173,8 @@ h3.mt-4 Docking
.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
.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(
[(ngModel)]='config.store.appearance.dockAlwaysOnTop',
(ngModelChange)='saveConfiguration(); docking.dock()',
@@ -182,7 +182,7 @@ h3.mt-4 Docking
.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
.header
.title Docked terminal size
.title(translate) Docked terminal size
input(
type='range',
[(ngModel)]='config.store.appearance.dockFill',
@@ -194,7 +194,7 @@ h3.mt-4 Docking
.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
.header
.title Docked terminal space
.title(translate) Docked terminal space
input(
type='range',
[(ngModel)]='config.store.appearance.dockSpace',
@@ -206,18 +206,18 @@ h3.mt-4 Docking
.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
.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(
[(ngModel)]='config.store.appearance.dockHideOnBlur',
(ngModelChange)='saveConfiguration(); ',
)
h3.mt-4 Tabs
h3.mt-4(translate) Tabs
.form-line
.header
.title Tabs location
.title(translate) Tabs location
.btn-group(
[(ngModel)]='config.store.appearance.tabsLocation',
(ngModelChange)='saveConfiguration()',
@@ -229,32 +229,32 @@ h3.mt-4 Tabs
ngbButton,
[value]='"top"'
)
| Top
span(translate) Top
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"bottom"'
)
| Bottom
span(translate) Bottom
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"left"'
)
| Left
span(translate) Left
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"right"'
)
| Right
span(translate) Right
.form-line
.header
.title Tabs width
.title(translate) Tabs width
.btn-group(
[(ngModel)]='config.store.appearance.flexTabs',
(ngModelChange)='saveConfiguration()',
@@ -266,18 +266,18 @@ h3.mt-4 Tabs
ngbButton,
[value]='true'
)
| Dynamic
span(translate) Dynamic
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='false'
)
| Fixed
span(translate) Fixed
.form-line
.header
.title Hide tab index
.title(translate) Hide tab index
toggle(
[(ngModel)]='config.store.terminal.hideTabIndex',
@@ -286,7 +286,7 @@ h3.mt-4 Tabs
.form-line
.header
.title Hide tab close button
.title(translate) Hide tab close button
toggle(
[(ngModel)]='config.store.terminal.hideCloseButton',
@@ -297,8 +297,8 @@ h3.mt-4 Hacks
.form-line
.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(
[(ngModel)]='config.store.hacks.disableGPU',

View File

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

View File

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