wip

上级 04010b58
......@@ -17,12 +17,15 @@
"author": "Eugene Pankov",
"license": "MIT",
"devDependencies": {
"@ngx-translate/core": "^14.0.0",
"bootstrap": "^4.1.3",
"deepmerge": "^4.1.1",
"js-yaml": "^4.0.0",
"messageformat": "^2.3.0",
"mixpanel": "^0.13.0",
"ngx-filesize": "^2.0.16",
"ngx-perfect-scrollbar": "^10.1.0",
"ngx-translate-messageformat-compiler": "^4.11.0",
"readable-stream": "3.6.0",
"uuid": "^8.0.0"
},
......
......@@ -35,4 +35,5 @@ export { TabsService, NewTabParameters, TabComponentType } from '../services/tab
export { UpdaterService } from '../services/updater.service'
export { VaultService, Vault, VaultSecret, VaultFileSecret, VAULT_SECRET_TYPE_FILE, StoredVault, VaultSecretKey } from '../services/vault.service'
export { FileProvidersService } from '../services/fileProviders.service'
export { LocaleService } from '../services/locale.service'
export * from '../utils'
......@@ -38,3 +38,4 @@ enableExperimentalFeatures: false
pluginBlacklist: []
hacks:
disableGPU: false
language: en
import { NgModule, ModuleWithProviders } from '@angular/core'
import { NgModule, ModuleWithProviders, LOCALE_ID } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { FormsModule } from '@angular/forms'
......@@ -7,6 +7,8 @@ import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-sc
import { NgxFilesizeModule } from 'ngx-filesize'
import { SortablejsModule } from 'ngx-sortablejs'
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 { CheckboxComponent } from './components/checkbox.component'
......@@ -40,6 +42,7 @@ import { AppService } from './services/app.service'
import { ConfigService } from './services/config.service'
import { VaultFileProvider } from './services/vault.service'
import { HotkeysService } from './services/hotkeys.service'
import { LocaleService, TranslateServiceWrapper } from './services/locale.service'
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
import { CoreConfigProvider } from './config'
......@@ -51,6 +54,10 @@ import { SplitLayoutProfilesService } from './profiles'
import 'perfect-scrollbar/css/perfect-scrollbar.css'
export function TranslateMessageFormatCompilerFactory (): TranslateMessageFormatCompiler {
return new TranslateMessageFormatCompiler()
}
const PROVIDERS = [
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
{ provide: Theme, useClass: StandardTheme, multi: true },
......@@ -68,6 +75,19 @@ const PROVIDERS = [
{ provide: FileProvider, useClass: VaultFileProvider, multi: true },
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, 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 */
......@@ -81,6 +101,7 @@ const PROVIDERS = [
PerfectScrollbarModule,
DragDropModule,
SortablejsModule.forRoot({ animation: 150 }),
TranslateModule,
],
declarations: [
AppRootComponent,
......@@ -127,6 +148,7 @@ const PROVIDERS = [
AlwaysVisibleTypeaheadDirective,
SortablejsModule,
DragDropModule,
TranslateModule,
],
})
export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
......@@ -198,9 +220,19 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex
}
static forRoot (): ModuleWithProviders<AppModule> {
const translateModule = TranslateModule.forRoot({
defaultLanguage: 'en',
compiler: {
provide: TranslateCompiler,
useFactory: TranslateMessageFormatCompilerFactory,
},
})
return {
ngModule: AppModule,
providers: PROVIDERS,
providers: [
...PROVIDERS,
...translateModule.providers!.filter(x => x !== TranslateService),
],
}
}
}
......
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()
})
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 {
this.setLocale(this.config.store.language)
}
async setLocale (lang: string): Promise<void> {
const strings = this.translations[lang]
if (!this.translate.langs.includes(lang)) {
this.translate.addLangs([lang])
// Filter out legacy interpolated strings
const filteredStrings = Object.fromEntries(
Object.entries(strings).filter(e => !e[0].includes('{{')),
)
this.translate.setTranslation(lang, filteredStrings)
}
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
}
}
......@@ -14,6 +14,7 @@ import { HotkeysService } from './services/hotkeys.service'
import { PromptModalComponent } from './components/promptModal.component'
import { SplitLayoutProfilesService } from './profiles'
import { TAB_COLORS } from './utils'
import { TranslateService } from '@ngx-translate/core'
/** @hidden */
@Injectable()
......@@ -22,6 +23,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
constructor (
private app: AppService,
private translate: TranslateService,
) {
super()
}
......@@ -29,7 +31,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
let items: MenuItemOptions[] = [
{
label: 'Close',
label: this.translate.instant('Close'),
click: () => {
if (this.app.tabs.includes(tab)) {
this.app.closeTab(tab, true)
......@@ -43,7 +45,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
items = [
...items,
{
label: 'Close other tabs',
label: this.translate.instant('Close other tabs'),
click: () => {
for (const t of this.app.tabs.filter(x => x !== tab)) {
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: () => {
for (const t of this.app.tabs.slice(this.app.tabs.indexOf(tab) + 1)) {
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: () => {
for (const t of this.app.tabs.slice(0, this.app.tabs.indexOf(tab))) {
this.app.closeTab(t, true)
......@@ -71,13 +73,13 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
if (tab.parent instanceof SplitTabComponent) {
const directions: SplitDirection[] = ['r', 'b', 'l', 't']
items.push({
label: 'Split',
label: this.translate.instant('Split'),
submenu: directions.map(dir => ({
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],
click: () => {
(tab.parent as SplitTabComponent).splitTab(tab, dir)
......@@ -99,6 +101,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
private app: AppService,
private ngbModal: NgbModal,
private splitLayoutProfilesService: SplitLayoutProfilesService,
private translate: TranslateService,
) {
super()
}
......@@ -109,18 +112,18 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
items = [
...items,
{
label: 'Rename',
label: this.translate.instant('Rename'),
click: () => tabHeader.showRenameTabModal(),
},
{
label: 'Duplicate',
label: this.translate.instant('Duplicate'),
click: () => this.app.duplicateTab(tab),
},
{
label: 'Color',
label: this.translate.instant('Color'),
sublabel: TAB_COLORS.find(x => x.value === tab.color)?.name,
submenu: TAB_COLORS.map(color => ({
label: color.name,
label: this.translate.instant(color.name),
type: 'radio',
checked: tab.color === color.value,
click: () => {
......@@ -132,10 +135,10 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
if (tab instanceof SplitTabComponent && tab.getAllTabs().length > 1) {
items.push({
label: 'Save layout as profile',
label: this.translate.instant('Save layout as profile'),
click: async () => {
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
if (!name) {
return
......@@ -154,6 +157,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
constructor (
private app: AppService,
private translate: TranslateService,
) {
super()
}
......@@ -167,10 +171,10 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
if (process) {
items.push({
enabled: false,
label: 'Current process: ' + process.name,
label: this.translate.instant('Current process: ' + process.name),
})
items.push({
label: 'Notify when done',
label: this.translate.instant('Notify when done'),
type: 'checkbox',
checked: extTab.__completionNotificationEnabled,
click: () => {
......@@ -178,7 +182,7 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
if (extTab.__completionNotificationEnabled) {
this.app.observeTabCompletion(tab).subscribe(() => {
new Notification('Process completed', {
new Notification(this.translate.instant('Process completed'), {
body: process.name,
}).addEventListener('click', () => {
this.app.selectTab(tab)
......@@ -192,7 +196,7 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
})
}
items.push({
label: 'Notify on activity',
label: this.translate.instant('Notify on activity'),
type: 'checkbox',
checked: !!extTab.__outputNotificationSubscription,
click: () => {
......@@ -204,7 +208,7 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
if (extTab.__outputNotificationSubscription && active) {
extTab.__outputNotificationSubscription.unsubscribe()
extTab.__outputNotificationSubscription = null
new Notification('Tab activity', {
new Notification(this.translate.instant('Tab activity'), {
body: tab.title,
}).addEventListener('click', () => {
this.app.selectTab(tab)
......
......@@ -2,6 +2,13 @@
# 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:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
......@@ -56,6 +63,37 @@ js-yaml@^4.0.0:
dependencies:
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:
version "0.13.0"
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"
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"
perfect-scrollbar@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.5.0.tgz#821d224ed8ff61990c23f26db63048cdc75b6b83"
......@@ -116,11 +161,21 @@ string_decoder@^1.1.1:
dependencies:
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:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
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:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
......
......@@ -66,6 +66,15 @@
h3 Application settings
.form-line
.header
.title Language
select.form-control([(ngModel)]='config.store.language', (ngModelChange)='saveConfiguration()')
option(
[value]='lang.code',
*ngFor='let lang of locale.allLanguages'
) {{lang.name|translate}}
.form-line(*ngIf='platform.isShellIntegrationSupported()')
.header
.title Shell integration
......
......@@ -12,6 +12,7 @@ import {
PlatformService,
HostWindowService,
AppService,
LocaleService,
} from 'tabby-core'
import { SettingsTabProvider } from '../api'
......@@ -43,6 +44,7 @@ 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[],
......
......@@ -47,6 +47,7 @@ module.exports = options => {
alias: options.alias ?? {},
modules: ['.', 'src', 'node_modules', '../app/node_modules', '../node_modules'].map(x => path.join(options.dirname, x)),
extensions: ['.ts', '.js'],
mainFields: ['esm2015', 'browser', 'module', 'main'],
},
ignoreWarnings: [/Failed to parse source map/],
module: {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册