Merge pull request #6033 from steindvart/settings-tabs

Organize settings page into horizontal tabs for improved usability
This commit is contained in:
Johannes Millan 2026-01-17 12:27:14 +01:00 committed by GitHub
commit 8580742272
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 241 additions and 136 deletions

View file

@ -24,18 +24,28 @@ const filterGlobalConfigForm = (cfg: ConfigFormSection<any>): boolean => {
);
};
export const GLOBAL_CONFIG_FORM_CONFIG: ConfigFormConfig = [
// Tab: General - Language, App Features, Misc, Short Syntax, Sound (specified separately in html)
export const GLOBAL_GENERAL_FORM_CONFIG: ConfigFormConfig = [
LANGUAGE_SELECTION_FORM_FORM,
APP_FEATURES_FORM_CFG,
MISC_SETTINGS_FORM_CFG,
SHORT_SYNTAX_FORM_CFG,
IDLE_FORM_CFG,
KEYBOARD_SETTINGS_FORM_CFG,
TIME_TRACKING_FORM_CFG,
REMINDER_FORM_CFG,
SCHEDULE_FORM_CFG,
].filter(filterGlobalConfigForm);
// Tab: Time & Tracking - Time Tracking, Idle, Schedule, Reminder
export const GLOBAL_TIME_TRACKING_FORM_CONFIG: ConfigFormConfig = [
TIME_TRACKING_FORM_CFG,
IDLE_FORM_CFG,
SCHEDULE_FORM_CFG,
REMINDER_FORM_CFG,
].filter(filterGlobalConfigForm);
// Tab: Plugins
export const GLOBAL_PLUGINS_FORM_CONFIG: ConfigFormConfig = [].filter(
filterGlobalConfigForm,
);
export const GLOBAL_IMEX_FORM_CONFIG: ConfigFormConfig = [
// NOTE: the backup form is added dynamically due to async prop required
IMEX_FORM,

View file

@ -1,90 +1,132 @@
<div class="page-settings page-wrapper">
@if (globalCfg) {
<div>
<div class="section-wrapper component-wrapper">
<h1 class="mat-h1">{{ T.PS.GLOBAL_SETTINGS | translate }}</h1>
<theme-selector></theme-selector>
@for (section of globalConfigFormCfg; track section.key) {
<section class="config-section section-{{ section.key }}">
<config-section
(save)="saveGlobalCfg($event)"
[cfg]="getGlobalCfgSection(section.key)"
[section]="section"
></config-section>
</section>
}
<section class="config-section">
<config-sound-form
[cfg]="getGlobalCfgSection('sound')"
(save)="saveGlobalCfg($event)"
></config-sound-form>
</section>
<div class="settings-container">
<h1 class="mat-h1 settings-title">{{ T.PS.GLOBAL_SETTINGS | translate }}</h1>
<div>
<h2
class="mat-h2"
style="margin-top: 32px"
>
{{ T.PS.PLUGINS | translate }}
</h2>
<section class="config-section plugin-section">
<collapsible
[isIconBefore]="true"
[isExpanded]="false"
[title]="T.PS.PLUGINS | translate"
>
<plugin-management></plugin-management>
</collapsible>
</section>
</div>
<div class="tour-productivityHelper">
<h2
class="mat-h2"
style="margin-top: 32px"
>
{{ T.PS.PRODUCTIVITY_HELPER | translate }}
</h2>
@for (section of globalProductivityConfigFormCfg; track section.key) {
<mat-tab-group
class="settings-tabs"
[(selectedIndex)]="selectedTabIndex"
animationDuration="200ms"
>
<!-- Tab: General -->
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tab-icon">settings</mat-icon>
<span class="tab-label">{{ 'PS.TABS.GENERAL' | translate }}</span>
</ng-template>
<div class="tab-content">
<theme-selector></theme-selector>
@for (section of generalFormCfg; track section.key) {
<section class="config-section section-{{ section.key }}">
<config-section
(save)="saveGlobalCfg($event)"
[cfg]="getGlobalCfgSection(section.key)"
[section]="section"
></config-section>
</section>
}
<!-- @todo: Create a single style for forming all the settings elements.
Make all as "sections" or as "forms" consistently.
-->
<section class="config-section">
<config-section
<config-sound-form
[cfg]="getGlobalCfgSection('sound')"
(save)="saveGlobalCfg($event)"
[cfg]="getGlobalCfgSection(section.key)"
[section]="section"
></config-section>
></config-sound-form>
</section>
}
</div>
</div>
</mat-tab>
<h2
class="mat-h2"
style="margin-top: 32px"
>
{{ T.PS.SYNC_EXPORT | translate }}
</h2>
<!-- Tab: Time & Tracking -->
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tab-icon">timer</mat-icon>
<span class="tab-label">{{ 'PS.TABS.TIME_TRACKING' | translate }}</span>
</ng-template>
<div class="tab-content">
@for (section of timeTrackingFormCfg; track section.key) {
<section class="config-section section-{{ section.key }}">
<config-section
(save)="saveGlobalCfg($event)"
[cfg]="getGlobalCfgSection(section.key)"
[section]="section"
></config-section>
</section>
}
</div>
</mat-tab>
<section class="config-section tour-syncSection">
<!-- NOTE: we need to debounce, as external updates end us in an endless loop -->
@let syncSettingsForm = syncSettingsService.syncSettingsForm$ | async;
@if (syncSettingsForm) {
<config-section
(save)="syncSettingsService.updateSettingsFromForm($event.config)"
[cfg]="syncSettingsForm"
[section]="globalSyncConfigFormCfg"
></config-section>
}
</section>
<!-- Tab: Productivity -->
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tab-icon">trending_up</mat-icon>
<span class="tab-label">{{ 'PS.TABS.PRODUCTIVITY' | translate }}</span>
</ng-template>
<div class="tab-content tour-productivityHelper">
@for (section of globalProductivityConfigFormCfg; track section.key) {
<section class="config-section">
<config-section
(save)="saveGlobalCfg($event)"
[cfg]="getGlobalCfgSection(section.key)"
[section]="section"
></config-section>
</section>
}
</div>
</mat-tab>
@for (section of globalImexFormCfg; track section.key) {
<section class="config-section imex-section">
<config-section
(save)="saveGlobalCfg($event)"
[cfg]="getGlobalCfgSection(section.key)"
[section]="section"
></config-section>
</section>
}
</div>
<!-- Tab: Plugins -->
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tab-icon">extension</mat-icon>
<span class="tab-label">{{ 'PS.TABS.PLUGINS' | translate }}</span>
</ng-template>
<div class="tab-content">
<section class="config-section plugin-section">
<plugin-management></plugin-management>
</section>
@for (section of pluginsShortcutsFormCfg; track section.key) {
<section class="config-section section-{{ section.key }}">
<config-section
(save)="saveGlobalCfg($event)"
[cfg]="getGlobalCfgSection(section.key)"
[section]="section"
></config-section>
</section>
}
</div>
</mat-tab>
<!-- Tab: Sync & Backup -->
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="tab-icon">cloud_sync</mat-icon>
<span class="tab-label">{{ 'PS.TABS.SYNC_BACKUP' | translate }}</span>
</ng-template>
<div class="tab-content">
<section class="config-section tour-syncSection">
@let syncSettingsForm = syncSettingsService.syncSettingsForm$ | async;
@if (syncSettingsForm) {
<config-section
(save)="syncSettingsService.updateSettingsFromForm($event.config)"
[cfg]="syncSettingsForm"
[section]="globalSyncConfigFormCfg"
></config-section>
}
</section>
@for (section of globalImexFormCfg; track section.key) {
<section class="config-section imex-section">
<config-section
(save)="saveGlobalCfg($event)"
[cfg]="getGlobalCfgSection(section.key)"
[section]="section"
></config-section>
</section>
}
</div>
</mat-tab>
</mat-tab-group>
</div>
}
</div>

View file

@ -3,14 +3,59 @@
.page-settings {
text-align: start;
collapsible {
position: relative;
.settings-container {
max-width: 100%;
}
.section-wrapper {
//display: grid;
//grid-template-columns: 50% 50%;
//grid-gap: 0 var(--s2);
.settings-title {
margin-bottom: var(--s2);
}
.settings-tabs {
::ng-deep .mat-mdc-tab-header {
border-bottom: 1px solid var(--divider-color);
margin-bottom: var(--s2);
}
::ng-deep .mat-mdc-tab {
min-width: 120px;
@include mq(xs, max) {
min-width: 90px;
padding: 0 8px;
}
}
.tab-icon {
margin-inline-end: 8px;
font-size: 20px;
width: 20px;
height: 20px;
@include mq(xs, max) {
margin-inline-end: 4px;
font-size: 18px;
width: 18px;
height: 18px;
}
}
.tab-label {
@include mq(xs, max) {
font-size: 12px;
}
}
}
.tab-content {
padding-top: var(--s2);
max-width: 900px;
margin-left: auto;
margin-right: auto;
}
collapsible {
position: relative;
}
.config-section {
@ -21,7 +66,6 @@
background: var(--card-bg);
background-color: var(--card-bg);
//background-color: var(--bg-slightly-lighter);
.md-title {
margin-top: 0;
@ -70,15 +114,9 @@
}
.collapsible-panel {
//border-top: 1px solid black;
overflow: visible;
> * {
// for help icon positioning
// does not work because of translate on the slide down ani element
// position: static;
// add a padding
padding: 0 $this-panel-padding-left-right;
}
}

View file

@ -9,9 +9,11 @@ import {
} from '@angular/core';
import { GlobalConfigService } from '../../features/config/global-config.service';
import {
GLOBAL_CONFIG_FORM_CONFIG,
GLOBAL_GENERAL_FORM_CONFIG,
GLOBAL_IMEX_FORM_CONFIG,
GLOBAL_PLUGINS_FORM_CONFIG,
GLOBAL_PRODUCTIVITY_FORM_CONFIG,
GLOBAL_TIME_TRACKING_FORM_CONFIG,
} from '../../features/config/global-config-form-config.const';
import {
ConfigFormConfig,
@ -49,12 +51,13 @@ import { SyncWrapperService } from '../../imex/sync/sync-wrapper.service';
import { UserProfileService } from '../../features/user-profile/user-profile.service';
import { MatDialog } from '@angular/material/dialog';
import { DialogDisableProfilesConfirmationComponent } from '../../features/user-profile/dialog-disable-profiles-confirmation/dialog-disable-profiles-confirmation.component';
import { SuperSyncRestoreService } from '../../imex/sync/super-sync-restore.service';
import { DialogRestorePointComponent } from '../../imex/sync/dialog-restore-point/dialog-restore-point.component';
import { LegacySyncProvider } from '../../imex/sync/legacy-sync-provider.model';
import { DialogChangeEncryptionPasswordComponent } from '../../imex/sync/dialog-change-encryption-password/dialog-change-encryption-password.component';
import { DialogConfirmComponent } from '../../ui/dialog-confirm/dialog-confirm.component';
import { LS } from '../../core/persistence/storage-keys.const';
import { MatTab, MatTabGroup, MatTabLabel } from '@angular/material/tabs';
import { MatIcon } from '@angular/material/icon';
@Component({
selector: 'config-page',
@ -69,22 +72,33 @@ import { LS } from '../../core/persistence/storage-keys.const';
AsyncPipe,
PluginManagementComponent,
CollapsibleComponent,
MatTabGroup,
MatTab,
MatTabLabel,
MatIcon,
],
})
export class ConfigPageComponent implements OnInit, OnDestroy {
private readonly _cd = inject(ChangeDetectorRef);
private readonly _providerManager = inject(SyncProviderManager);
readonly configService = inject(GlobalConfigService);
readonly syncSettingsService = inject(SyncConfigService);
private readonly _syncWrapperService = inject(SyncWrapperService);
private readonly _pluginBridgeService = inject(PluginBridgeService);
private readonly _snackService = inject(SnackService);
private readonly _userProfileService = inject(UserProfileService);
private readonly _matDialog = inject(MatDialog);
private readonly _superSyncRestoreService = inject(SuperSyncRestoreService);
readonly configService = inject(GlobalConfigService);
readonly syncSettingsService = inject(SyncConfigService);
T: typeof T = T;
globalConfigFormCfg: ConfigFormConfig;
selectedTabIndex = 0;
// @todo - find better names for tabs configs forms
// Tab-specific form configurations
generalFormCfg: ConfigFormConfig;
timeTrackingFormCfg: ConfigFormConfig;
pluginsShortcutsFormCfg: ConfigFormConfig;
globalImexFormCfg: ConfigFormConfig;
globalProductivityConfigFormCfg: ConfigFormConfig;
globalSyncConfigFormCfg = this._buildSyncFormConfig();
@ -114,8 +128,10 @@ export class ConfigPageComponent implements OnInit, OnDestroy {
private _subs: Subscription = new Subscription();
constructor() {
// somehow they are only unproblematic if assigned here
this.globalConfigFormCfg = GLOBAL_CONFIG_FORM_CONFIG.slice();
// Initialize tab-specific form configurations
this.generalFormCfg = GLOBAL_GENERAL_FORM_CONFIG.slice();
this.timeTrackingFormCfg = GLOBAL_TIME_TRACKING_FORM_CONFIG.slice();
this.pluginsShortcutsFormCfg = GLOBAL_PLUGINS_FORM_CONFIG.slice();
this.globalImexFormCfg = GLOBAL_IMEX_FORM_CONFIG.slice();
this.globalProductivityConfigFormCfg = GLOBAL_PRODUCTIVITY_FORM_CONFIG.slice();
@ -150,8 +166,9 @@ export class ConfigPageComponent implements OnInit, OnDestroy {
}
private _updateKeyboardFormWithPluginShortcuts(shortcuts: PluginShortcutCfg[]): void {
// Find keyboard form section
const keyboardFormIndex = this.globalConfigFormCfg.findIndex(
// @todo - make separate core shortcuts and plugins shortcuts settings
// Find keyboard form section in general tab configuration
const keyboardFormIndex = this.generalFormCfg.findIndex(
(section) => section.key === 'keyboard',
);
@ -160,7 +177,7 @@ export class ConfigPageComponent implements OnInit, OnDestroy {
return;
}
const keyboardSection = this.globalConfigFormCfg[keyboardFormIndex];
const keyboardSection = this.generalFormCfg[keyboardFormIndex];
// Remove existing plugin shortcuts and header from the form
const filteredItems = (keyboardSection.items || []).filter((item) => {
@ -196,10 +213,10 @@ export class ConfigPageComponent implements OnInit, OnDestroy {
};
// Create a new config array to ensure Angular detects the change
this.globalConfigFormCfg = [
...this.globalConfigFormCfg.slice(0, keyboardFormIndex),
this.generalFormCfg = [
...this.generalFormCfg.slice(0, keyboardFormIndex),
newKeyboardSection,
...this.globalConfigFormCfg.slice(keyboardFormIndex + 1),
...this.generalFormCfg.slice(keyboardFormIndex + 1),
];
// Trigger change detection

View file

@ -2196,11 +2196,17 @@ const T = {
NO_PLUGINS_INSTALLED: 'PS.NO_PLUGINS_INSTALLED',
PLUGINS: 'PS.PLUGINS',
PRIVACY_POLICY: 'PS.PRIVACY_POLICY',
PRODUCTIVITY_HELPER: 'PS.PRODUCTIVITY_HELPER',
PROJECT_SETTINGS: 'PS.PROJECT_SETTINGS',
PROVIDE_FEEDBACK: 'PS.PROVIDE_FEEDBACK',
RELOAD: 'PS.RELOAD',
SYNC_EXPORT: 'PS.SYNC_EXPORT',
TABS: {
GENERAL: 'PS.TABS.GENERAL',
TIME_TRACKING: 'PS.TABS.TIME_TRACKING',
PRODUCTIVITY: 'PS.TABS.PRODUCTIVITY',
PLUGINS: 'PS.TABS.PLUGINS',
SYNC_BACKUP: 'PS.TABS.SYNC_BACKUP',
},
TAG_SETTINGS: 'PS.TAG_SETTINGS',
TOGGLE_DARK_MODE: 'PS.TOGGLE_DARK_MODE',
UPDATE_APP: 'PS.UPDATE_APP',

View file

@ -1895,7 +1895,6 @@
"NO_PLUGINS_INSTALLED": "لا توجد إضافات مثبتة حاليا",
"PLUGINS": "الإضافات",
"PRIVACY_POLICY": "سياسة الخصوصية",
"PRODUCTIVITY_HELPER": "مساعد الإنتاجية",
"PROJECT_SETTINGS": "إعدادات محددة للمشروع",
"PROVIDE_FEEDBACK": "تقديم التغذية الراجعة",
"SYNC_EXPORT": "المزامنة والتصدير",

View file

@ -1132,7 +1132,6 @@
"PS": {
"GLOBAL_SETTINGS": "Celková nastavení",
"PRIVACY_POLICY": "Zásady ochrany osobních údajů",
"PRODUCTIVITY_HELPER": "Pomocník produktivity",
"PROJECT_SETTINGS": "Nastavení specifické pro projekt",
"PROVIDE_FEEDBACK": "Poskytnout zpětnou vazbu",
"SYNC_EXPORT": "Synchronizace a export",

View file

@ -1975,7 +1975,6 @@
"NO_PLUGINS_INSTALLED": "Derzeit sind keine Plugins installiert",
"PLUGINS": "Plugins",
"PRIVACY_POLICY": "Datenschutzerklärung",
"PRODUCTIVITY_HELPER": "Produktivitäts-Helfer",
"PROJECT_SETTINGS": "Projektspezifische Einstellungen",
"PROVIDE_FEEDBACK": "Rückmeldung geben",
"SYNC_EXPORT": "Synchronisieren und exportieren",

View file

@ -2181,11 +2181,17 @@
"NO_PLUGINS_INSTALLED": "No plugins are currently installed",
"PLUGINS": "Plugins",
"PRIVACY_POLICY": "Privacy Policy",
"PRODUCTIVITY_HELPER": "Productivity Helper",
"PROJECT_SETTINGS": "Project Specific Settings",
"PROVIDE_FEEDBACK": "Provide Feedback",
"RELOAD": "Reload",
"SYNC_EXPORT": "Sync & Export",
"TABS": {
"GENERAL": "General",
"TIME_TRACKING": "Time & Tracking",
"PRODUCTIVITY": "Productivity",
"PLUGINS": "Plugins",
"SYNC_BACKUP": "Sync & Backup"
},
"TAG_SETTINGS": "Tag Specific Settings",
"TOGGLE_DARK_MODE": "Toggle Dark Mode",
"UPDATE_APP": "Update App"

View file

@ -1975,7 +1975,6 @@
"NO_PLUGINS_INSTALLED": "No hay plugins instalados actualmente",
"PLUGINS": "Plugins",
"PRIVACY_POLICY": "Política de Privacidad",
"PRODUCTIVITY_HELPER": "Ayudante de Productividad",
"PROJECT_SETTINGS": "Ajustes Específicos del Proyecto",
"PROVIDE_FEEDBACK": "Proporcionar Feedback",
"SYNC_EXPORT": "Sincronizar y Exportar",

View file

@ -1895,7 +1895,6 @@
"NO_PLUGINS_INSTALLED": "در حال حاضر هیچ پلاگینی نصب نشده است",
"PLUGINS": "پلاگین",
"PRIVACY_POLICY": "Privacy Policy",
"PRODUCTIVITY_HELPER": "Productivity Helper",
"PROJECT_SETTINGS": "Project Specific Settings",
"PROVIDE_FEEDBACK": "ارائه بازخورد",
"SYNC_EXPORT": "همگام سازی و صادرات",

View file

@ -1977,7 +1977,6 @@
"NO_PLUGINS_INSTALLED": "Laajennuksia ei ole asennettuna tällä hetkellä",
"PLUGINS": "Laajennukset",
"PRIVACY_POLICY": "Tietosuojakäytäntö",
"PRODUCTIVITY_HELPER": "Tuottavuusavustaja",
"PROJECT_SETTINGS": "Projektikohtaiset asetukset",
"PROVIDE_FEEDBACK": "Anna palautetta",
"SYNC_EXPORT": "Synkronointi & Vienti",

View file

@ -1977,7 +1977,6 @@
"NO_PLUGINS_INSTALLED": "Aucun plugin n'est actuellement installé",
"PLUGINS": "Plugins",
"PRIVACY_POLICY": "Politique privée",
"PRODUCTIVITY_HELPER": "Aide à la productivité",
"PROJECT_SETTINGS": "Paramètres spécifiques au projet",
"PROVIDE_FEEDBACK": "Fournir une réponse",
"SYNC_EXPORT": "Synchroniser et exporter",

View file

@ -1526,7 +1526,6 @@
"PS": {
"GLOBAL_SETTINGS": "Globalne postavke",
"PRIVACY_POLICY": "Politika privatnosti",
"PRODUCTIVITY_HELPER": "Pomoćnik produktivnosti",
"PROJECT_SETTINGS": "Postavke određenog projekta",
"PROVIDE_FEEDBACK": "Pošalji povratne informacije",
"SYNC_EXPORT": "Sinkronizacija i izvoz",

View file

@ -1977,7 +1977,6 @@
"NO_PLUGINS_INSTALLED": "Tidak ada plugin yang terpasang saat ini",
"PLUGINS": "Plugin",
"PRIVACY_POLICY": "Kebijakan pribadi",
"PRODUCTIVITY_HELPER": "Pembantu Produktivitas",
"PROJECT_SETTINGS": "Pengaturan Khusus Proyek",
"PROVIDE_FEEDBACK": "Berikan umpan balik",
"SYNC_EXPORT": "Sinkron & Ekspor",

View file

@ -1977,7 +1977,6 @@
"NO_PLUGINS_INSTALLED": "Nessun plugin attualmente installato",
"PLUGINS": "Plugin",
"PRIVACY_POLICY": "Politica sulla Privacy",
"PRODUCTIVITY_HELPER": "Aiuto produttività",
"PROJECT_SETTINGS": "Impostazioni specifiche del progetto",
"PROVIDE_FEEDBACK": "La tua opinione",
"SYNC_EXPORT": "Sincronizza ed esporta",

View file

@ -1886,7 +1886,6 @@
"PS": {
"GLOBAL_SETTINGS": "全体設定",
"PRIVACY_POLICY": "プライベートポリシー",
"PRODUCTIVITY_HELPER": "生産性ヘルパー",
"PROJECT_SETTINGS": "プロジェクト固有の設定",
"PROVIDE_FEEDBACK": "フィードバックを提供します",
"SYNC_EXPORT": "同期とエクスポート",

View file

@ -1554,7 +1554,6 @@
"PS": {
"GLOBAL_SETTINGS": "전체 설정",
"PRIVACY_POLICY": "개인 정책",
"PRODUCTIVITY_HELPER": "생산성 도우미",
"PROJECT_SETTINGS": "프로젝트 별 설정",
"PROVIDE_FEEDBACK": "피드백을 제공하다",
"SYNC_EXPORT": "동기화 및 내보내기",

View file

@ -1977,7 +1977,6 @@
"NO_PLUGINS_INSTALLED": "Ingen plugins er installert for øyeblikket",
"PLUGINS": "Plugins",
"PRIVACY_POLICY": "Privat policy",
"PRODUCTIVITY_HELPER": "Produktivitetshjelper",
"PROJECT_SETTINGS": "Prosjektspesifikke innstillinger",
"PROVIDE_FEEDBACK": "Gi tilbakemelding",
"SYNC_EXPORT": "Synkroniser og eksporter",

View file

@ -1977,7 +1977,6 @@
"NO_PLUGINS_INSTALLED": "Er zijn momenteel geen plugins geïnstalleerd",
"PLUGINS": "Plugins",
"PRIVACY_POLICY": "Privébeleid",
"PRODUCTIVITY_HELPER": "Productiviteitshulp",
"PROJECT_SETTINGS": "Projectspecifieke instellingen",
"PROVIDE_FEEDBACK": "Geef feedback",
"SYNC_EXPORT": "Synchroniseren en exporteren",

View file

@ -2024,7 +2024,6 @@
"NO_PLUGINS_INSTALLED": "Obecnie nie ma zainstalowanych wtyczek",
"PLUGINS": "Wtyczki",
"PRIVACY_POLICY": "Polityka Prywatności",
"PRODUCTIVITY_HELPER": "Pomocnik Produktywności",
"PROJECT_SETTINGS": "Ustawienia dla Projektu",
"PROVIDE_FEEDBACK": "Prześlij Opinię",
"SYNC_EXPORT": "Synchronizacja i Eksport",

View file

@ -1977,7 +1977,6 @@
"NO_PLUGINS_INSTALLED": "Nenhum plugin está instalado no momento",
"PLUGINS": "Plugins",
"PRIVACY_POLICY": "Política de Privacidade",
"PRODUCTIVITY_HELPER": "Auxiliar de produtividade",
"PROJECT_SETTINGS": "Configurações específicas do projeto",
"PROVIDE_FEEDBACK": "Fornecer feedback",
"SYNC_EXPORT": "Sincronizar e Exportar",

View file

@ -1977,7 +1977,6 @@
"NO_PLUGINS_INSTALLED": "Nenhum plugin está atualmente instalado",
"PLUGINS": "Plugins",
"PRIVACY_POLICY": "Política Privada",
"PRODUCTIVITY_HELPER": "Auxiliar de produtividade",
"PROJECT_SETTINGS": "Configurações específicas do projeto",
"PROVIDE_FEEDBACK": "Dar uma resposta",
"SYNC_EXPORT": "Sincronizar e exportar",

View file

@ -1979,11 +1979,19 @@
"NO_PLUGINS_INSTALLED": "Нет установленных плагинов",
"PLUGINS": "Плагины",
"PRIVACY_POLICY": "Политика конфиденциальности",
"PRODUCTIVITY_HELPER": "Помощник производительности",
"PROJECT_SETTINGS": "Настройки проекта",
"PROVIDE_FEEDBACK": "Обратная связь",
"SYNC_EXPORT": "Синхронизация и экспорт",
"TAG_SETTINGS": "Специальные настройки тегов"
"TABS": {
"GENERAL": "Общие",
"TIME_TRACKING": "Время и отслеживание",
"PRODUCTIVITY": "Продуктивность",
"PLUGINS": "Плагины",
"SYNC_BACKUP": "Синхронизация и резервирование"
},
"TAG_SETTINGS": "Специальные настройки тега",
"TOGGLE_DARK_MODE": "Переключить темный режим",
"UPDATE_APP": "Обновить приложение"
},
"SCHEDULE": {
"LAST": "Прошедшие:",

View file

@ -1977,7 +1977,6 @@
"NO_PLUGINS_INSTALLED": "Momentálne nie sú nainštalované žiadne pluginy",
"PLUGINS": "Pluginy",
"PRIVACY_POLICY": "Privacy Policy",
"PRODUCTIVITY_HELPER": "Productivity Helper",
"PROJECT_SETTINGS": "Project Specific Settings",
"PROVIDE_FEEDBACK": "Provide Feedback",
"SYNC_EXPORT": "Sync & Export",

View file

@ -1824,7 +1824,6 @@
"NO_PLUGINS_INSTALLED": "Inga plugins är för närvarande installerade",
"PLUGINS": "Plugins",
"PRIVACY_POLICY": "Sekretesspolicy",
"PRODUCTIVITY_HELPER": "Produktivitetshjälp",
"PROJECT_SETTINGS": "Projektspecifika inställningar",
"PROVIDE_FEEDBACK": "Ge feedback",
"SYNC_EXPORT": "Synkronisera och exportera",

View file

@ -1956,7 +1956,6 @@
"NO_PLUGINS_INSTALLED": "Şu an yüklenmiş eklenti yok.",
"PLUGINS": "Eklentiler",
"PRIVACY_POLICY": "Gizlilik sözleşmesi",
"PRODUCTIVITY_HELPER": "Verimlilik Yardımcısı",
"PROJECT_SETTINGS": "Projeye Özel Ayarlar",
"PROVIDE_FEEDBACK": "Geribildirim sağlayın",
"SYNC_EXPORT": "Senkronize Et ve Dışa Aktar",

View file

@ -1539,7 +1539,6 @@
"PS": {
"GLOBAL_SETTINGS": "Глобальні налаштування",
"PRIVACY_POLICY": "Політика конфіденційності",
"PRODUCTIVITY_HELPER": "Помічник продуктивності",
"PROJECT_SETTINGS": "Налаштування конкретного проекту",
"PROVIDE_FEEDBACK": "Надати відгук",
"SYNC_EXPORT": "Синхронізація та експорт",

View file

@ -2379,7 +2379,6 @@
"NO_PLUGINS_INSTALLED": "当前未安装任何插件",
"PLUGINS": "插件",
"PRIVACY_POLICY": "隐私政策",
"PRODUCTIVITY_HELPER": "生产力助手",
"PROJECT_SETTINGS": "项目特定设置",
"PROVIDE_FEEDBACK": "提供反馈",
"RELOAD": "重新加载",