feat: Rename to Toju and add translation
Some checks failed
Deploy Web Apps / deploy (push) Successful in 5m52s
Build Android APK / build-android-apk (push) Failing after 23m15s
Queue Release Build / prepare (push) Successful in 1m42s
Queue Release Build / build-linux (push) Failing after 9m33s
Queue Release Build / build-windows (push) Successful in 26m5s
Queue Release Build / finalize (push) Has been skipped
Some checks failed
Deploy Web Apps / deploy (push) Successful in 5m52s
Build Android APK / build-android-apk (push) Failing after 23m15s
Queue Release Build / prepare (push) Successful in 1m42s
Queue Release Build / build-linux (push) Failing after 9m33s
Queue Release Build / build-windows (push) Successful in 26m5s
Queue Release Build / finalize (push) Has been skipped
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { SettingsModalService } from '../../../../../core/services/settings-modal.service';
|
||||
import { AppI18nService, APP_TRANSLATE_IMPORTS } from '../../../../../core/i18n';
|
||||
import { getElectronApi } from '../../../../../core/platform/electron/get-electron-api';
|
||||
import {
|
||||
ThemeContainerKey,
|
||||
@@ -40,7 +41,8 @@ type ThemeStudioWorkspace = 'editor' | 'inspector' | 'layout';
|
||||
imports: [
|
||||
CommonModule,
|
||||
ThemeGridEditorComponent,
|
||||
ThemeJsonCodeEditorComponent
|
||||
ThemeJsonCodeEditorComponent,
|
||||
...APP_TRANSLATE_IMPORTS
|
||||
],
|
||||
templateUrl: './theme-settings.component.html',
|
||||
styleUrl: './theme-settings.component.scss'
|
||||
@@ -52,6 +54,7 @@ export class ThemeSettingsComponent {
|
||||
readonly registry = inject(ThemeRegistryService);
|
||||
readonly picker = inject(ElementPickerService);
|
||||
readonly layoutSync = inject(LayoutSyncService);
|
||||
private readonly appI18n = inject(AppI18nService);
|
||||
|
||||
readonly editorRef = viewChild<ThemeJsonCodeEditorComponent>('jsonEditorRef');
|
||||
|
||||
@@ -68,23 +71,23 @@ export class ThemeSettingsComponent {
|
||||
readonly animationKeys = this.theme.knownAnimationClasses;
|
||||
readonly layoutContainers = this.layoutSync.containers();
|
||||
readonly themeEntries = this.registry.entries();
|
||||
readonly workspaceTabs: readonly { key: ThemeStudioWorkspace; label: string; description: string }[] = [
|
||||
readonly workspaceTabs = computed(() => [
|
||||
{
|
||||
key: 'editor',
|
||||
label: 'JSON Editor',
|
||||
description: 'Edit the raw theme document in a fixed-contrast code view.'
|
||||
key: 'editor' as const,
|
||||
label: this.appI18n.instant('theme.studio.workspaces.editor.label'),
|
||||
description: this.appI18n.instant('theme.studio.workspaces.editor.description')
|
||||
},
|
||||
{
|
||||
key: 'inspector',
|
||||
label: 'Element Inspector',
|
||||
description: 'Browse themeable regions, supported overrides, and starter values.'
|
||||
key: 'inspector' as const,
|
||||
label: this.appI18n.instant('theme.studio.workspaces.inspector.label'),
|
||||
description: this.appI18n.instant('theme.studio.workspaces.inspector.description')
|
||||
},
|
||||
{
|
||||
key: 'layout',
|
||||
label: 'Layout Studio',
|
||||
description: 'Move shells around the grid without hunting through JSON.'
|
||||
key: 'layout' as const,
|
||||
label: this.appI18n.instant('theme.studio.workspaces.layout.label'),
|
||||
description: this.appI18n.instant('theme.studio.workspaces.layout.description')
|
||||
}
|
||||
];
|
||||
]);
|
||||
readonly mountedKeyCounts = computed(() => this.registry.mountedKeyCounts());
|
||||
readonly activeWorkspace = signal<ThemeStudioWorkspace>('editor');
|
||||
readonly activeEditorTab = signal<ThemeEditorTab>('json');
|
||||
@@ -102,10 +105,10 @@ export class ThemeSettingsComponent {
|
||||
}
|
||||
|
||||
return [
|
||||
selected.layoutEditable ? 'Layout editable' : null,
|
||||
selected.supportsTextOverride ? 'Text override' : null,
|
||||
selected.supportsLink ? 'Safe external link' : null,
|
||||
selected.supportsIcon ? 'Icon slot' : null
|
||||
selected.layoutEditable ? this.appI18n.instant('theme.studio.capabilities.layoutEditable') : null,
|
||||
selected.supportsTextOverride ? this.appI18n.instant('theme.studio.capabilities.textOverride') : null,
|
||||
selected.supportsLink ? this.appI18n.instant('theme.studio.capabilities.safeExternalLink') : null,
|
||||
selected.supportsIcon ? this.appI18n.instant('theme.studio.capabilities.iconSlot') : null
|
||||
].filter((value): value is string => value !== null);
|
||||
});
|
||||
readonly selectedContainerItems = computed(() => this.layoutSync.itemsForContainer(this.selectedContainer()));
|
||||
@@ -116,7 +119,15 @@ export class ThemeSettingsComponent {
|
||||
return this.selectedContainerItems().find((item) => item.key === this.selectedElementKey()) ?? null;
|
||||
});
|
||||
readonly activeWorkspaceInfo = computed(() => {
|
||||
return this.workspaceTabs.find((workspace) => workspace.key === this.activeWorkspace()) ?? this.workspaceTabs[0];
|
||||
return this.workspaceTabs().find((workspace) => workspace.key === this.activeWorkspace()) ?? this.workspaceTabs()[0];
|
||||
});
|
||||
readonly localizedFilteredEntries = computed(() =>
|
||||
this.filteredEntries().map((entry) => this.localizeRegistryEntry(entry))
|
||||
);
|
||||
readonly localizedSelectedElement = computed(() => {
|
||||
const selected = this.selectedElement();
|
||||
|
||||
return selected ? this.localizeRegistryEntry(selected) : null;
|
||||
});
|
||||
readonly visiblePropertyHints = computed(() => {
|
||||
const selected = this.selectedElement();
|
||||
@@ -260,6 +271,10 @@ export class ThemeSettingsComponent {
|
||||
this.setWorkspace(select.value as ThemeStudioWorkspace);
|
||||
}
|
||||
|
||||
layoutContainerLabel(key: string): string {
|
||||
return this.appI18n.instant(`theme.registry.${key}.label`);
|
||||
}
|
||||
|
||||
onExplorerQueryInput(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
|
||||
@@ -281,7 +296,9 @@ export class ThemeSettingsComponent {
|
||||
const fileName = `${this.sanitizeThemeFileName(this.draftTheme().meta.name)}.json`;
|
||||
const saved = await this.saveTextAsFile(fileName, exportText);
|
||||
|
||||
this.theme.announceStatus(saved ? `${fileName} exported.` : 'Theme export cancelled.');
|
||||
this.theme.announceStatus(saved
|
||||
? this.appI18n.instant('theme.studio.exported', { fileName })
|
||||
: this.appI18n.instant('theme.studio.exportCancelled'));
|
||||
}
|
||||
|
||||
importThemeFile(event: Event): void {
|
||||
@@ -300,7 +317,9 @@ export class ThemeSettingsComponent {
|
||||
async copyLlmThemeGuide(): Promise<void> {
|
||||
const copied = await this.copyTextToClipboard(THEME_LLM_GUIDE);
|
||||
|
||||
this.setLlmGuideCopyMessage(copied ? 'LLM guide copied.' : 'Manual copy opened.');
|
||||
this.setLlmGuideCopyMessage(copied
|
||||
? this.appI18n.instant('theme.studio.llmGuideCopied')
|
||||
: this.appI18n.instant('theme.studio.llmGuideManualCopy'));
|
||||
}
|
||||
|
||||
startPicker(): void {
|
||||
@@ -501,7 +520,7 @@ export class ThemeSettingsComponent {
|
||||
}
|
||||
|
||||
if (!this.draftIsValid()) {
|
||||
this.theme.announceStatus('Fix JSON errors before exporting the theme.');
|
||||
this.theme.announceStatus(this.appI18n.instant('theme.studio.fixJsonBeforeExport'));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -532,7 +551,7 @@ export class ThemeSettingsComponent {
|
||||
this.syncCssOnlyTextFromTheme();
|
||||
this.focusEditor();
|
||||
} catch {
|
||||
this.theme.announceStatus(`Unable to import ${file.name}.`);
|
||||
this.theme.announceStatus(this.appI18n.instant('theme.status.importFailed', { fileName: file.name }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,13 +792,13 @@ export class ThemeSettingsComponent {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
window.prompt('Copy this LLM theme guide', value);
|
||||
window.prompt(this.appI18n.instant('theme.studio.llmGuidePrompt'), value);
|
||||
return false;
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
|
||||
window.prompt('Copy this LLM theme guide', value);
|
||||
window.prompt(this.appI18n.instant('theme.studio.llmGuidePrompt'), value);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -796,6 +815,34 @@ export class ThemeSettingsComponent {
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
presetDisplayName(presetKey: string, fallbackName: string): string {
|
||||
const localized = this.appI18n.instant(`theme.presets.${presetKey}.name`);
|
||||
|
||||
return localized === `theme.presets.${presetKey}.name` ? fallbackName : localized;
|
||||
}
|
||||
|
||||
presetDescription(presetKey: string, fallbackDescription?: string): string | undefined {
|
||||
if (!fallbackDescription) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const localized = this.appI18n.instant(`theme.presets.${presetKey}.description`);
|
||||
|
||||
return localized === `theme.presets.${presetKey}.description` ? fallbackDescription : localized;
|
||||
}
|
||||
|
||||
isDefaultPresetName(name: string): boolean {
|
||||
return name === this.appI18n.instant('theme.presets.toju-default-dark-11.name');
|
||||
}
|
||||
|
||||
private localizeRegistryEntry(entry: ThemeRegistryEntry): ThemeRegistryEntry {
|
||||
return {
|
||||
...entry,
|
||||
label: this.appI18n.instant(`theme.registry.${entry.key}.label`),
|
||||
description: this.appI18n.instant(`theme.registry.${entry.key}.description`)
|
||||
};
|
||||
}
|
||||
|
||||
private findAnchorIndex(text: string, section: JumpSection, key: string): number {
|
||||
const sectionAnchor = `"${section}": {`;
|
||||
const sectionIndex = text.indexOf(sectionAnchor);
|
||||
|
||||
Reference in New Issue
Block a user