mirror of
https://github.com/yeban8090/book-smith.git
synced 2026-07-22 05:46:23 +00:00
国际化
This commit is contained in:
parent
49b91bbb29
commit
452a16b58b
37 changed files with 1839 additions and 478 deletions
|
|
@ -3,6 +3,7 @@ import { Book, ChapterNode } from '../types/book';
|
|||
import { BookManager } from '../services/BookManager';
|
||||
import { NamePromptModal } from '../modals/NamePromptModal';
|
||||
import { ConfirmModal } from '../modals/ConfirmModal';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class ChapterTree {
|
||||
// === 属性 ===
|
||||
|
|
@ -28,13 +29,13 @@ export class ChapterTree {
|
|||
const menu = new Menu();
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("新建文件");
|
||||
item.setTitle(i18n.t('NEW_FILE'));
|
||||
item.setIcon("file-plus");
|
||||
item.onClick(() => this.createNode('file'));
|
||||
});
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("新建文件夹");
|
||||
item.setTitle(i18n.t('NEW_FOLDER'));
|
||||
item.setIcon("folder-plus");
|
||||
item.onClick(() => this.createNode('group'));
|
||||
});
|
||||
|
|
@ -215,7 +216,7 @@ export class ChapterTree {
|
|||
// 为文件夹类型添加创建选项
|
||||
if (node.type === 'group') {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("新建文件");
|
||||
item.setTitle(i18n.t('NEW_FILE'));
|
||||
item.setIcon("file-plus");
|
||||
item.onClick(() => {
|
||||
this.createNode('file', node.path, node);
|
||||
|
|
@ -223,7 +224,7 @@ export class ChapterTree {
|
|||
});
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("新建文件夹");
|
||||
item.setTitle(i18n.t('NEW_FOLDER'));
|
||||
item.setIcon("folder-plus");
|
||||
item.onClick(() => {
|
||||
this.createNode('group', node.path, node);
|
||||
|
|
@ -235,7 +236,7 @@ export class ChapterTree {
|
|||
|
||||
if (node.type === 'file') {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("在新标签页中打开");
|
||||
item.setTitle(i18n.t('OPEN_IN_NEW_TAB'));
|
||||
item.setIcon("file-plus");
|
||||
item.onClick(() => {
|
||||
if (file instanceof TFile) {
|
||||
|
|
@ -245,7 +246,7 @@ export class ChapterTree {
|
|||
});
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("在新标签组中打开");
|
||||
item.setTitle(i18n.t('OPEN_IN_NEW_PANE'));
|
||||
item.setIcon("files");
|
||||
item.onClick(() => {
|
||||
if (file instanceof TFile) {
|
||||
|
|
@ -259,7 +260,9 @@ export class ChapterTree {
|
|||
// 只有当文件不排除之外时,才显示"标记完成章节"选项
|
||||
if (!node.exclude) {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(node.default_status === 'done' ? "标记重新创作" : "标记完成章节");
|
||||
item.setTitle(node.default_status === 'done' ?
|
||||
i18n.t('MARK_AS_DRAFT') :
|
||||
i18n.t('MARK_AS_COMPLETE'));
|
||||
item.setIcon(node.default_status === 'done' ? "x-circle" : "check-circle");
|
||||
item.onClick(async () => {
|
||||
node.default_status = node.default_status === 'done' ? 'draft' : 'done';
|
||||
|
|
@ -281,7 +284,9 @@ export class ChapterTree {
|
|||
|
||||
// 添加排除选项
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(node.exclude ? "包含在统计与导出中" : "排除在统计与导出外");
|
||||
item.setTitle(node.exclude ?
|
||||
i18n.t('INCLUDE_IN_STATS') :
|
||||
i18n.t('EXCLUDE_FROM_STATS'));
|
||||
item.setIcon(node.exclude ? "plus-circle" : "minus-circle");
|
||||
item.onClick(async () => {
|
||||
node.exclude = !node.exclude;
|
||||
|
|
@ -301,8 +306,8 @@ export class ChapterTree {
|
|||
});
|
||||
await this.onDragComplete?.();
|
||||
new Notice(node.exclude ?
|
||||
`已将"${node.title}"排除` :
|
||||
`已将"${node.title}"包含`);
|
||||
i18n.t('EXCLUDED_NOTICE', { title: node.title }) :
|
||||
i18n.t('INCLUDED_NOTICE', { title: node.title }));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -310,13 +315,13 @@ export class ChapterTree {
|
|||
}
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("创建副本");
|
||||
item.setTitle(i18n.t('CREATE_COPY'));
|
||||
item.setIcon("copy");
|
||||
item.onClick(async () => {
|
||||
try {
|
||||
const parentPath = node.path.substring(0, node.path.lastIndexOf('/'));
|
||||
const baseName = node.title;
|
||||
const newName = `${baseName} 副本`;
|
||||
const newName = i18n.t('COPY_NAME', { name: baseName });
|
||||
const newPath = parentPath
|
||||
? `${this.bookPath}/${parentPath}/${newName}${node.type === 'file' ? '.md' : ''}`
|
||||
: `${this.bookPath}/${newName}${node.type === 'file' ? '.md' : ''}`;
|
||||
|
|
@ -354,22 +359,21 @@ export class ChapterTree {
|
|||
structure: this.book.structure
|
||||
});
|
||||
await this.onDragComplete?.();
|
||||
new Notice('创建副本成功');
|
||||
new Notice(i18n.t('COPY_SUCCESS'));
|
||||
} catch (error) {
|
||||
new Notice(`创建副本失败: ${error.message}`);
|
||||
new Notice(i18n.t('COPY_FAILED', { error: error.message }));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("重命名");
|
||||
item.setTitle(i18n.t('RENAME'));
|
||||
item.setIcon("pencil");
|
||||
item.onClick(async () => {
|
||||
// 获取当前文件名(不含扩展名)
|
||||
const currentName = node.type === 'file'
|
||||
? node.title.replace(/\.md$/, '')
|
||||
: node.title;
|
||||
const newName = await this.promptForName("请输入新名称", currentName);
|
||||
const newName = await this.promptForName(i18n.t('ENTER_NEW_NAME'), currentName);
|
||||
if (!newName) return;
|
||||
|
||||
try {
|
||||
|
|
@ -394,21 +398,23 @@ export class ChapterTree {
|
|||
});
|
||||
|
||||
await this.onDragComplete?.();
|
||||
new Notice('重命名成功');
|
||||
new Notice(i18n.t('RENAME_SUCCESS'));
|
||||
} catch (error) {
|
||||
new Notice(`重命名失败: ${error.message}`);
|
||||
new Notice(i18n.t('RENAME_FAILED', { error: error.message }));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("删除");
|
||||
item.setTitle(i18n.t('DELETE'));
|
||||
item.setIcon("trash");
|
||||
item.onClick(() => {
|
||||
const title = node.type === 'file' ? "删除文件" : "删除文件夹";
|
||||
const title = node.type === 'file' ?
|
||||
i18n.t('DELETE_FILE_TITLE') :
|
||||
i18n.t('DELETE_FOLDER_TITLE');
|
||||
const message = node.type === 'file'
|
||||
? `确定要删除文件 "${node.title}" 吗?\n它将被移动到系统回收站。`
|
||||
: `确定要删除文件夹 "${node.title}" 及其所有内容吗?\n它们将被移动到系统回收站。`;
|
||||
? i18n.t('DELETE_FILE_DESC', { title: node.title })
|
||||
: i18n.t('DELETE_FOLDER_DESC', { title: node.title });
|
||||
|
||||
new ConfirmModal(this.app, title, message, async () => {
|
||||
try {
|
||||
|
|
@ -418,9 +424,9 @@ export class ChapterTree {
|
|||
structure: this.book.structure
|
||||
});
|
||||
await this.onDragComplete?.();
|
||||
new Notice('删除成功');
|
||||
new Notice(i18n.t('DELETE_SUCCESS'));
|
||||
} catch (error) {
|
||||
new Notice(`删除失败: ${error.message}`);
|
||||
new Notice(i18n.t('DELETE_FAILED', { error: error.message }));
|
||||
}
|
||||
}).open();
|
||||
});
|
||||
|
|
@ -541,18 +547,18 @@ export class ChapterTree {
|
|||
|
||||
const sourceFile = this.app.vault.getAbstractFileByPath(sourcePath);
|
||||
if (!sourceFile) {
|
||||
throw new Error('源文件不存在');
|
||||
throw new Error(i18n.t('SOURCE_NOT_FOUND'));
|
||||
}
|
||||
|
||||
const existingFile = this.app.vault.getAbstractFileByPath(targetPath);
|
||||
if (existingFile && existingFile !== sourceFile) {
|
||||
throw new Error('目标位置已存在同名文件');
|
||||
throw new Error(i18n.t('TARGET_EXISTS'));
|
||||
}
|
||||
|
||||
if (position === 'inside') {
|
||||
const targetFolder = this.app.vault.getAbstractFileByPath(`${this.bookPath}/${targetNode.path}`);
|
||||
if (!targetFolder) {
|
||||
throw new Error('目标文件夹不存在');
|
||||
throw new Error(i18n.t('TARGET_FOLDER_NOT_FOUND'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -564,7 +570,7 @@ export class ChapterTree {
|
|||
await this.onDragComplete?.();
|
||||
} catch (error) {
|
||||
console.error('移动失败:', error);
|
||||
new Notice(`移动失败`);
|
||||
new Notice(i18n.t('MOVE_FAILED'));
|
||||
} finally {
|
||||
this.draggedNode = null;
|
||||
}
|
||||
|
|
@ -632,7 +638,9 @@ export class ChapterTree {
|
|||
parentNode?: ChapterNode
|
||||
) {
|
||||
const isFile = type === 'file';
|
||||
const name = await this.promptForName(`请输入${isFile ? '文件' : '文件夹'}名`);
|
||||
const name = await this.promptForName(
|
||||
isFile ? i18n.t('ENTER_FILE_NAME') : i18n.t('ENTER_FOLDER_NAME')
|
||||
);
|
||||
if (!name) return;
|
||||
|
||||
const newPath = parentPath
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
import { setIcon, App, Notice } from 'obsidian';
|
||||
import { setIcon, App } from 'obsidian';
|
||||
import { ConfirmModal } from '../modals/ConfirmModal';
|
||||
import { FocusManager, FocusState } from '../services/FocusManager';
|
||||
import BookSmithPlugin from '../main';
|
||||
|
||||
interface FocusStats {
|
||||
completedSessions: number;
|
||||
interruptions: number;
|
||||
totalWords: number;
|
||||
}
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class FocusToolView {
|
||||
// ================ 属性定义 ================
|
||||
|
|
@ -51,7 +46,7 @@ export class FocusToolView {
|
|||
private createHeader() {
|
||||
const header = this.container.createDiv({ cls: 'focus-tool-header' });
|
||||
setIcon(header.createSpan({ cls: 'focus-tool-header-icon' }), 'target');
|
||||
header.createSpan({ text: '专注模式', cls: 'focus-tool-title' });
|
||||
header.createSpan({ text: i18n.t('FOCUS_MODE'), cls: 'focus-tool-title' });
|
||||
}
|
||||
|
||||
private createTimerSection() {
|
||||
|
|
@ -107,7 +102,7 @@ export class FocusToolView {
|
|||
|
||||
private createStatusSection() {
|
||||
this.statusEl = this.container.createDiv({ cls: 'focus-tool-status' });
|
||||
this.statusEl.setText('准备开始');
|
||||
this.statusEl.setText(i18n.t('READY_TO_START'));
|
||||
}
|
||||
|
||||
private createControlSection() {
|
||||
|
|
@ -116,23 +111,23 @@ export class FocusToolView {
|
|||
}
|
||||
|
||||
private createInitialControls(controls: HTMLElement) {
|
||||
this.createButton(controls, '开始专注', true, () => {
|
||||
this.createButton(controls, i18n.t('START_FOCUS'), true, () => {
|
||||
this.focusManager.startFocus();
|
||||
controls.empty();
|
||||
this.createControlButtons(controls);
|
||||
});
|
||||
this.createButton(controls, '退出', false, () => this.handleExit());
|
||||
this.createButton(controls, i18n.t('EXIT'), false, () => this.handleExit());
|
||||
}
|
||||
|
||||
private createStatsSection() {
|
||||
const statsContainer = this.container.createDiv({ cls: 'focus-tool-stats-container' });
|
||||
const stats = this.focusManager.getStats();
|
||||
|
||||
this.completedEl = this.createStatItem(statsContainer, '专注次数', stats.completedSessions.toString());
|
||||
this.interruptedEl = this.createStatItem(statsContainer, '中断次数', stats.interruptions.toString());
|
||||
this.wordCountEl = this.createStatItem(statsContainer, '当前字数', '0');
|
||||
this.wordGoalEl = this.createStatItem(statsContainer, '目标字数', this.plugin.settings.focus.wordGoal.toString());
|
||||
this.totalWordsEl = this.createStatItem(statsContainer, '专注总字数', stats.totalWords.toString());
|
||||
this.completedEl = this.createStatItem(statsContainer, i18n.t('FOCUS_SESSIONS'), stats.completedSessions.toString());
|
||||
this.interruptedEl = this.createStatItem(statsContainer, i18n.t('INTERRUPTIONS'), stats.interruptions.toString());
|
||||
this.wordCountEl = this.createStatItem(statsContainer, i18n.t('CURRENT_WORDS'), '0');
|
||||
this.wordGoalEl = this.createStatItem(statsContainer, i18n.t('WORD_GOAL'), this.plugin.settings.focus.wordGoal.toString());
|
||||
this.totalWordsEl = this.createStatItem(statsContainer, i18n.t('TOTAL_FOCUS_WORDS'), stats.totalWords.toString());
|
||||
}
|
||||
|
||||
private createControlButtons(controls: HTMLElement) {
|
||||
|
|
@ -140,13 +135,13 @@ export class FocusToolView {
|
|||
const state = this.focusManager.getState();
|
||||
|
||||
if (state === FocusState.WORKING) {
|
||||
this.createButton(controls, '暂停', false, () => this.focusManager.pauseFocus());
|
||||
this.createButton(controls, i18n.t('PAUSE'), false, () => this.focusManager.pauseFocus());
|
||||
} else if (state === FocusState.PAUSED) {
|
||||
this.createButton(controls, '继续', true, () => this.focusManager.resumeFocus());
|
||||
this.createButton(controls, '结束', false, () => this.showEndConfirmation());
|
||||
this.createButton(controls, i18n.t('RESUME'), true, () => this.focusManager.resumeFocus());
|
||||
this.createButton(controls, i18n.t('END'), false, () => this.showEndConfirmation());
|
||||
}
|
||||
|
||||
this.createButton(controls, '退出', false, () => this.handleExit());
|
||||
this.createButton(controls, i18n.t('EXIT'), false, () => this.handleExit());
|
||||
}
|
||||
|
||||
private createButton(container: HTMLElement, text: string, solid: boolean, onClick: () => void) {
|
||||
|
|
@ -311,8 +306,8 @@ export class FocusToolView {
|
|||
if (state === FocusState.WORKING || state === FocusState.PAUSED) {
|
||||
new ConfirmModal(
|
||||
this.app,
|
||||
'退出专注',
|
||||
'确定要退出专注吗?当前专注进度将会丢失。',
|
||||
i18n.t('EXIT_FOCUS'),
|
||||
i18n.t('EXIT_FOCUS_DESC'),
|
||||
() => {
|
||||
this.focusManager.endFocus();
|
||||
this.onExit();
|
||||
|
|
@ -329,8 +324,8 @@ export class FocusToolView {
|
|||
private showEndConfirmation() {
|
||||
new ConfirmModal(
|
||||
this.app,
|
||||
'结束专注',
|
||||
'确定要结束专注吗?这将计入中断次数。',
|
||||
i18n.t('END_FOCUS'),
|
||||
i18n.t('END_FOCUS_DESC'),
|
||||
() => {
|
||||
this.focusManager.endFocus();
|
||||
this.onExit();
|
||||
|
|
@ -341,10 +336,10 @@ export class FocusToolView {
|
|||
// ================ 工具方法 ================
|
||||
private getStatusText(state: FocusState): string {
|
||||
switch (state) {
|
||||
case FocusState.WORKING: return '专注中';
|
||||
case FocusState.PAUSED: return '已暂停';
|
||||
case FocusState.BREAK: return '休息时间';
|
||||
default: return '准备开始';
|
||||
case FocusState.WORKING: return i18n.t('FOCUSING');
|
||||
case FocusState.PAUSED: return i18n.t('PAUSED');
|
||||
case FocusState.BREAK: return i18n.t('BREAK_TIME');
|
||||
default: return i18n.t('READY_TO_START');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
95
src/i18n/i18n.ts
Normal file
95
src/i18n/i18n.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { moment } from 'obsidian';
|
||||
import { Translation } from './interfaces';
|
||||
|
||||
// 支持的语言
|
||||
export type Locale = 'zh-CN' | 'en' | 'ja' | 'ko' | 'fr' | 'de' | 'es' | 'pt' | 'ru';
|
||||
|
||||
// 语言管理器
|
||||
export class I18n {
|
||||
private locale: Locale;
|
||||
private translations: Record<Locale, Translation>;
|
||||
|
||||
constructor() {
|
||||
// 初始化为系统语言或默认为中文
|
||||
this.locale = this.getSystemLocale();
|
||||
this.translations = {} as Record<Locale, Translation>;
|
||||
this.loadTranslations();
|
||||
}
|
||||
|
||||
// 获取系统语言
|
||||
private getSystemLocale(): Locale {
|
||||
const systemLocale = moment.locale();
|
||||
|
||||
// 映射 moment 语言代码到我们支持的语言
|
||||
if (systemLocale.startsWith('zh')) return 'zh-CN';
|
||||
if (systemLocale.startsWith('en')) return 'en';
|
||||
if (systemLocale.startsWith('ja')) return 'ja';
|
||||
if (systemLocale.startsWith('ko')) return 'ko';
|
||||
if (systemLocale.startsWith('fr')) return 'fr';
|
||||
if (systemLocale.startsWith('de')) return 'de';
|
||||
if (systemLocale.startsWith('es')) return 'es';
|
||||
if (systemLocale.startsWith('pt')) return 'pt';
|
||||
if (systemLocale.startsWith('ru')) return 'ru';
|
||||
|
||||
// 默认英文
|
||||
return 'en';
|
||||
}
|
||||
|
||||
// 设置当前语言
|
||||
public setLocale(locale: Locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
// 获取当前语言
|
||||
public getLocale(): Locale {
|
||||
return this.locale;
|
||||
}
|
||||
|
||||
// 加载翻译文件
|
||||
private loadTranslations() {
|
||||
// 导入所有语言文件
|
||||
import('./locales/zh-CN').then(module => {
|
||||
this.translations['zh-CN'] = module.default;
|
||||
});
|
||||
|
||||
import('./locales/en').then(module => {
|
||||
this.translations['en'] = module.default;
|
||||
});
|
||||
|
||||
// 其他语言可以在这里添加
|
||||
// import('./locales/ja').then(module => {
|
||||
// this.translations['ja'] = module.default;
|
||||
// });
|
||||
// ...
|
||||
}
|
||||
|
||||
// 获取翻译文本
|
||||
public t(key: keyof Translation, params?: Record<string, any>): string {
|
||||
let text: string;
|
||||
|
||||
// 如果当前语言的翻译不存在,回退到英文
|
||||
if (!this.translations[this.locale]) {
|
||||
text = this.translations['en'][key] || key as string;
|
||||
} else {
|
||||
// 如果当前语言的特定翻译不存在,回退到英文
|
||||
text = this.translations[this.locale][key] ||
|
||||
this.translations['en'][key] ||
|
||||
key as string;
|
||||
}
|
||||
|
||||
// 如果有参数,替换文本中的占位符
|
||||
if (params) {
|
||||
Object.keys(params).forEach(paramKey => {
|
||||
text = text.replace(new RegExp(`{${paramKey}}`, 'g'), params[paramKey]);
|
||||
});
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
export const i18n = new I18n();
|
||||
|
||||
// 导出接口
|
||||
export * from './interfaces';
|
||||
10
src/i18n/interfaces/common.ts
Normal file
10
src/i18n/interfaces/common.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// 通用翻译接口
|
||||
export interface CommonTranslation {
|
||||
// 通用
|
||||
PLUGIN_NAME: string;
|
||||
SETTINGS: string;
|
||||
SAVE: string;
|
||||
CANCEL: string;
|
||||
HIDE: string;
|
||||
SHOW: string;
|
||||
}
|
||||
60
src/i18n/interfaces/components/components.ts
Normal file
60
src/i18n/interfaces/components/components.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
export interface ComponentTranslation {
|
||||
// ChapterTree
|
||||
NEW_FILE: string;
|
||||
NEW_FOLDER: string;
|
||||
ENTER_FILE_NAME: string;
|
||||
ENTER_FOLDER_NAME: string;
|
||||
OPEN_IN_NEW_TAB: string;
|
||||
OPEN_IN_NEW_PANE: string;
|
||||
MARK_AS_COMPLETE: string;
|
||||
MARK_AS_DRAFT: string;
|
||||
EXCLUDE_FROM_STATS: string;
|
||||
INCLUDE_IN_STATS: string;
|
||||
CREATE_COPY: string;
|
||||
COPY_NAME: string; // 添加这一行
|
||||
RENAME: string;
|
||||
DELETE: string;
|
||||
DELETE_FILE_TITLE: string;
|
||||
DELETE_FILE_DESC: string;
|
||||
DELETE_FOLDER_TITLE: string;
|
||||
DELETE_FOLDER_DESC: string;
|
||||
COPY_SUCCESS: string;
|
||||
COPY_FAILED: string;
|
||||
RENAME_SUCCESS: string;
|
||||
RENAME_FAILED: string;
|
||||
DELETE_SUCCESS: string;
|
||||
DELETE_FAILED: string;
|
||||
MOVE_FAILED: string;
|
||||
SOURCE_NOT_FOUND: string;
|
||||
TARGET_EXISTS: string;
|
||||
TARGET_FOLDER_NOT_FOUND: string;
|
||||
EXCLUDED_NOTICE: string;
|
||||
INCLUDED_NOTICE: string;
|
||||
ENTER_NEW_NAME: string;
|
||||
|
||||
// FocusToolView
|
||||
FOCUS_MODE: string;
|
||||
START_FOCUS: string;
|
||||
EXIT: string;
|
||||
FOCUS_SESSIONS: string;
|
||||
INTERRUPTIONS: string;
|
||||
CURRENT_WORDS: string;
|
||||
WORD_GOAL: string;
|
||||
TOTAL_FOCUS_WORDS: string;
|
||||
PAUSE: string;
|
||||
RESUME: string;
|
||||
END: string;
|
||||
ENCOURAGEMENT_1: string;
|
||||
ENCOURAGEMENT_2: string;
|
||||
ENCOURAGEMENT_3: string;
|
||||
ENCOURAGEMENT_4: string;
|
||||
ENCOURAGEMENT_5: string;
|
||||
EXIT_FOCUS: string;
|
||||
EXIT_FOCUS_DESC: string;
|
||||
END_FOCUS: string;
|
||||
END_FOCUS_DESC: string;
|
||||
FOCUSING: string;
|
||||
PAUSED: string;
|
||||
BREAK_TIME: string;
|
||||
READY_TO_START: string;
|
||||
}
|
||||
28
src/i18n/interfaces/index.ts
Normal file
28
src/i18n/interfaces/index.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// 导出所有翻译接口
|
||||
import { CommonTranslation } from './common';
|
||||
import { BookSmithViewTranslation } from './views/booksmith-view';
|
||||
import { ToolViewTranslation } from './views/tool-view';
|
||||
import { SettingsTranslation } from './settings/settings';
|
||||
import { ModalTranslation } from './modals/modals';
|
||||
import { ToolbarModalTranslation } from './modals/toolbarModals';
|
||||
import { ManagerTranslation } from './manager/managers';
|
||||
import { ComponentTranslation } from './components/components';
|
||||
// 合并所有翻译接口
|
||||
export interface Translation extends
|
||||
CommonTranslation,
|
||||
BookSmithViewTranslation,
|
||||
ToolViewTranslation,
|
||||
SettingsTranslation,
|
||||
ModalTranslation,
|
||||
ToolbarModalTranslation,
|
||||
ManagerTranslation,
|
||||
ComponentTranslation{}
|
||||
|
||||
export * from './common';
|
||||
export * from './views/booksmith-view';
|
||||
export * from './views/tool-view';
|
||||
export * from './settings/settings';
|
||||
export * from './modals/modals';
|
||||
export * from './modals/toolbarModals';
|
||||
export * from './manager/managers';
|
||||
export * from './components/components';
|
||||
36
src/i18n/interfaces/manager/managers.ts
Normal file
36
src/i18n/interfaces/manager/managers.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
export interface ManagerTranslation {
|
||||
// BookManager 相关
|
||||
BOOK_EXISTS: string;
|
||||
BOOK_NOT_FOUND: string;
|
||||
BOOK_FOLDER_NOT_FOUND: string;
|
||||
SAVE_CONFIG_FAILED: string;
|
||||
IMPORT_BOOK_FAILED: string;
|
||||
UNKNOWN_AUTHOR: string;
|
||||
|
||||
// TemplateManager 相关
|
||||
TEMPLATE_TYPE_NOT_FOUND: string;
|
||||
TEMPLATE_EXISTS: string;
|
||||
TEMPLATE_SAVE_FAILED: string;
|
||||
|
||||
// FileManager 相关
|
||||
CREATE_FOLDER_FAILED: string;
|
||||
CREATE_FILE_FAILED: string;
|
||||
READ_FILE_FAILED: string;
|
||||
WRITE_FILE_FAILED: string;
|
||||
|
||||
// FocusManager 相关
|
||||
BREAK_TIME_START: string;
|
||||
FOCUS_SUMMARY: string;
|
||||
|
||||
// ReferenceManager 相关
|
||||
REFERENCE_FILE_NAME: string;
|
||||
REFERENCE_FILE_NOT_FOUND: string;
|
||||
REFERENCE_FILE_ERROR: string;
|
||||
SELECT_TEXT_TO_REFERENCE: string;
|
||||
CHAPTER_INFO_ERROR: string;
|
||||
|
||||
// ReferenceManager 菜单项
|
||||
EDIT_REFERENCE: string;
|
||||
DELETE_REFERENCE: string;
|
||||
INSERT_REFERENCE: string;
|
||||
}
|
||||
111
src/i18n/interfaces/modals/modals.ts
Normal file
111
src/i18n/interfaces/modals/modals.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// 模态框翻译接口
|
||||
export interface ModalTranslation {
|
||||
// 通用
|
||||
COVER: string;
|
||||
COVER_DESC: string;
|
||||
BOOK_TITLE: string;
|
||||
BOOK_TITLE_DESC: string;
|
||||
BOOK_TITLE_PLACEHOLDER: string;
|
||||
SUBTITLE: string;
|
||||
SUBTITLE_DESC: string;
|
||||
SUBTITLE_PLACEHOLDER: string;
|
||||
TARGET_WORDS: string;
|
||||
TARGET_WORDS_DESC: string;
|
||||
TARGET_WORDS_PLACEHOLDER: string;
|
||||
AUTHOR: string;
|
||||
AUTHOR_DESC: string;
|
||||
AUTHOR_PLACEHOLDER: string;
|
||||
DESCRIPTION: string;
|
||||
DESCRIPTION_DESC: string;
|
||||
DESCRIPTION_PLACEHOLDER: string;
|
||||
REQUIRED_FIELDS: string;
|
||||
|
||||
// CreateBookModal
|
||||
CREATE_BOOK_TITLE: string;
|
||||
BOOK_TEMPLATE: string;
|
||||
TEMPLATE_CHECK_DESC: string;
|
||||
SELECT_IMAGE: string;
|
||||
COVER_UPLOAD_SUCCESS: string;
|
||||
COVER_UPLOAD_FAILED: string;
|
||||
CREATE: string;
|
||||
CREATE_SUCCESS: string;
|
||||
CREATE_FAILED: string;
|
||||
|
||||
// EditBookModal
|
||||
EDIT_BOOK_TITLE: string;
|
||||
CHANGE_COVER: string;
|
||||
SELECT_COVER: string;
|
||||
COVER_UPDATE_SUCCESS: string;
|
||||
COVER_UPDATE_FAILED: string;
|
||||
SAVE: string;
|
||||
SAVE_SUCCESS: string;
|
||||
SAVE_FAILED: string;
|
||||
|
||||
// ManageBooksModal
|
||||
MANAGE_BOOKS_TITLE: string;
|
||||
SEARCH_BOOKS_PLACEHOLDER: string;
|
||||
IMPORT_BOOK: string;
|
||||
BOOK_AUTHOR_PREFIX: string;
|
||||
BOOK_DESC_PREFIX: string;
|
||||
BOOK_PROGRESS_PREFIX: string;
|
||||
DELETE_BOOK: string;
|
||||
EDIT_BOOK: string;
|
||||
DELETE_BOOK_TITLE: string;
|
||||
DELETE_BOOK_DESC: string;
|
||||
DELETE_SUCCESS: string;
|
||||
DELETE_FAILED: string;
|
||||
BOOKS_ROOT_NOT_FOUND: string;
|
||||
NO_UNIMPORTED_BOOKS: string;
|
||||
DETECT_UNIMPORTED_FAILED: string;
|
||||
IMPORT_SUCCESS: string;
|
||||
IMPORT_FAILED: string;
|
||||
|
||||
// SwitchBookModal
|
||||
SWITCH_BOOK_TITLE: string;
|
||||
SEARCH_BOOK_PLACEHOLDER: string;
|
||||
BOOK_AUTHOR_LABEL: string;
|
||||
BOOK_PROGRESS_LABEL: string;
|
||||
BOOK_WORDCOUNT_LABEL: string;
|
||||
BOOK_LASTMOD_LABEL: string;
|
||||
SELECT_BOOK: string;
|
||||
|
||||
// UnimportedBooksModal
|
||||
UNIMPORTED_BOOKS_TITLE: string;
|
||||
NO_UNIMPORTED_FOLDERS: string;
|
||||
CLOSE: string;
|
||||
SEARCH_FOLDERS_PLACEHOLDER: string;
|
||||
NO_MATCHING_FOLDERS: string;
|
||||
|
||||
IMPORT: string;
|
||||
SELECT_FOLDER_FIRST: string;
|
||||
|
||||
// ReferenceModal
|
||||
REFERENCE_MODAL_TITLE: string;
|
||||
REFERENCE_CONTENT: string;
|
||||
REFERENCE_CONTENT_DESC: string;
|
||||
// ConfirmModal
|
||||
// NamePromptModal
|
||||
NAME_LABEL: string;
|
||||
CONFIRM: string;
|
||||
CANCEL: string;
|
||||
|
||||
// TemplateEditModal
|
||||
TEMPLATE_EDIT_TITLE: string;
|
||||
TEMPLATE_CREATE_TITLE: string;
|
||||
TEMPLATE_NAME: string;
|
||||
TEMPLATE_NAME_PLACEHOLDER: string;
|
||||
TEMPLATE_DESC: string;
|
||||
TEMPLATE_DESC_PLACEHOLDER: string;
|
||||
TEMPLATE_STRUCTURE: string;
|
||||
ADD_FILE: string;
|
||||
ADD_FOLDER: string;
|
||||
DELETE_FOLDER_CONFIRM: string;
|
||||
NEW_CHAPTER: string;
|
||||
NEW_DIRECTORY: string;
|
||||
ENTER_NAME_PLACEHOLDER: string;
|
||||
TEMPLATE_NAME_REQUIRED: string;
|
||||
TEMPLATE_NODE_REQUIRED: string;
|
||||
TEMPLATE_NAME_EXISTS: string;
|
||||
TEMPLATE_SAVE_SUCCESS: string;
|
||||
TEMPLATE_SAVE_FAILED: string;
|
||||
}
|
||||
55
src/i18n/interfaces/modals/toolbarModals.ts
Normal file
55
src/i18n/interfaces/modals/toolbarModals.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
export interface ToolbarModalTranslation {
|
||||
// CommunityModal
|
||||
COMMUNITY_TITLE: string;
|
||||
COMMUNITY_INTRO: string;
|
||||
FEATURE_SHARE_TITLE: string;
|
||||
FEATURE_SHARE_DESC: string;
|
||||
FEATURE_DISCUSS_TITLE: string;
|
||||
FEATURE_DISCUSS_DESC: string;
|
||||
FEATURE_CHALLENGE_TITLE: string;
|
||||
FEATURE_CHALLENGE_DESC: string;
|
||||
JOIN_SECTION_TITLE: string;
|
||||
JOIN_SECTION_DESC: string;
|
||||
OFFICIAL_ACCOUNT: string;
|
||||
COPY_ACCOUNT: string;
|
||||
|
||||
// ContactModal
|
||||
ABOUT_AUTHOR: string;
|
||||
AUTHOR_INTRO_1: string;
|
||||
AUTHOR_INTRO_2_1: string;
|
||||
AUTHOR_INTRO_2_2: string;
|
||||
AUTHOR_INTRO_2_3: string;
|
||||
AUTHOR_INTRO_2_4: string;
|
||||
AUTHOR_INTRO_2_5: string;
|
||||
AUTHOR_INTRO_3: string;
|
||||
AUTHOR_INTRO_4_1: string;
|
||||
AUTHOR_INTRO_4_2: string;
|
||||
DONATE_TEXT: string;
|
||||
DONATE_BUTTON: string;
|
||||
MORE_INFO_TEXT: string;
|
||||
CONTACT_TITLE: string;
|
||||
CONTACT_WECHAT_OFFICIAL: string;
|
||||
CONTACT_XIAOHONGSHU: string;
|
||||
CONTACT_WECHAT: string;
|
||||
CONTACT_GITHUB: string;
|
||||
COPY_SUCCESS: string;
|
||||
COPY_FAILED: string;
|
||||
|
||||
// DonateModal
|
||||
DONATE_MODAL_TITLE: string;
|
||||
COMMUNITY_STATS_TITLE: string;
|
||||
COMMUNITY_STATS_USERS: string;
|
||||
COMMUNITY_STATS_WORDS: string;
|
||||
|
||||
DONATE_AMOUNT_COFFEE: string;
|
||||
DONATE_AMOUNT_CHAPTER: string;
|
||||
DONATE_AMOUNT_FEATURE: string;
|
||||
DONATE_FEEDBACK_COFFEE: string;
|
||||
DONATE_FEEDBACK_CHAPTER: string;
|
||||
DONATE_FEEDBACK_FEATURE: string;
|
||||
|
||||
PAYMENT_WECHAT: string;
|
||||
PAYMENT_ALIPAY: string;
|
||||
PAYMENT_KOFI: string;
|
||||
CURRENCY_UNIT: string;
|
||||
}
|
||||
36
src/i18n/interfaces/settings/settings.ts
Normal file
36
src/i18n/interfaces/settings/settings.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
export interface SettingsTranslation {
|
||||
// 设置页面
|
||||
SETTINGS_TITLE: string;
|
||||
BASIC_SETTINGS: string;
|
||||
TEMPLATE_SETTINGS: string;
|
||||
WRITING_TOOLS_SETTINGS: string;
|
||||
|
||||
// 基本设置
|
||||
LANGUAGE_SETTING: string;
|
||||
LANGUAGE_DESC: string;
|
||||
DEFAULT_AUTHOR: string;
|
||||
DEFAULT_AUTHOR_DESC: string;
|
||||
DEFAULT_AUTHOR_PLACEHOLDER: string;
|
||||
BOOK_STORAGE_PATH: string;
|
||||
BOOK_STORAGE_DESC: string;
|
||||
STORAGE_PATH_CHANGED: string;
|
||||
|
||||
// 模板设置
|
||||
DEFAULT_TEMPLATE: string;
|
||||
DEFAULT_TEMPLATE_DESC: string;
|
||||
BOOK_TEMPLATES: string;
|
||||
ADD_NEW_TEMPLATE: string;
|
||||
EDIT_TEMPLATE: string;
|
||||
DELETE_TEMPLATE: string;
|
||||
DELETE_TEMPLATE_TITLE: string;
|
||||
DELETE_TEMPLATE_DESC: string;
|
||||
|
||||
// 写作工具箱设置
|
||||
FOCUS_MODE_SETTINGS: string;
|
||||
FOCUS_DURATION: string;
|
||||
FOCUS_DURATION_DESC: string;
|
||||
BREAK_DURATION: string;
|
||||
BREAK_DURATION_DESC: string;
|
||||
WORD_GOAL: string;
|
||||
WORD_GOAL_DESC: string;
|
||||
}
|
||||
49
src/i18n/interfaces/views/booksmith-view.ts
Normal file
49
src/i18n/interfaces/views/booksmith-view.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// 书籍管理视图翻译接口
|
||||
export interface BookSmithViewTranslation {
|
||||
// 主界面
|
||||
BOOK_MANAGER: string;
|
||||
NEW_BOOK: string;
|
||||
SWITCH_BOOK: string;
|
||||
MANAGE_BOOK: string;
|
||||
|
||||
// 书籍相关
|
||||
BOOK_TITLE: string;
|
||||
BOOK_AUTHOR: string;
|
||||
BOOK_DESCRIPTION: string;
|
||||
BOOK_TAGS: string;
|
||||
BOOK_COVER: string;
|
||||
|
||||
// 章节相关
|
||||
CHAPTER: string;
|
||||
ADD_CHAPTER: string;
|
||||
DELETE_CHAPTER: string;
|
||||
RENAME_CHAPTER: string;
|
||||
|
||||
// 统计相关
|
||||
STATS: string;
|
||||
WORD_COUNT: string;
|
||||
CHAPTER_COUNT: string;
|
||||
|
||||
// 帮助提示
|
||||
HELP_TOOLTIP: string;
|
||||
|
||||
// 通知消息
|
||||
SWITCHED_TO_BOOK: string;
|
||||
IMPORTED_AND_SWITCHED: string;
|
||||
CURRENT_BOOK_DELETED: string;
|
||||
NO_BOOKS_TO_SWITCH: string;
|
||||
|
||||
// 统计文本
|
||||
TODAY_WORDS: string;
|
||||
TOTAL_WORDS: string;
|
||||
CHAPTER_COMPLETION: string;
|
||||
WRITING_DAYS: string;
|
||||
AVERAGE_DAILY_WORDS: string;
|
||||
WORD_UNIT: string;
|
||||
DAY_UNIT: string;
|
||||
TEN_THOUSAND: string;
|
||||
|
||||
// 空状态提示
|
||||
WELCOME_MESSAGE: string;
|
||||
EMPTY_STATE_HINT: string;
|
||||
}
|
||||
28
src/i18n/interfaces/views/tool-view.ts
Normal file
28
src/i18n/interfaces/views/tool-view.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// 工具箱视图翻译接口
|
||||
export interface ToolViewTranslation {
|
||||
WRITING_TOOLBOX: string;
|
||||
|
||||
// 写作助手
|
||||
WRITING_ASSISTANT: string;
|
||||
FOCUS_MODE: string;
|
||||
CREATIVE_INSPIRATION: string;
|
||||
CHARACTER_PROFILES: string;
|
||||
WORLD_BUILDING: string;
|
||||
|
||||
// 导出发布
|
||||
EXPORT_PUBLISH: string;
|
||||
DESIGN_TYPOGRAPHY: string;
|
||||
GENERATE_EBOOK: string;
|
||||
MORE_FEATURES: string;
|
||||
MORE_FEATURES_MESSAGE: string;
|
||||
|
||||
// 写作圈子
|
||||
WRITING_COMMUNITY: string;
|
||||
CREATIVE_COMMUNITY: string;
|
||||
CONTACT_AUTHOR: string;
|
||||
DONATE_SUPPORT: string;
|
||||
|
||||
// 面板设置
|
||||
PANEL_SETTINGS: string;
|
||||
FEATURE_COMING_SOON: string;
|
||||
}
|
||||
445
src/i18n/locales/en.ts
Normal file
445
src/i18n/locales/en.ts
Normal file
|
|
@ -0,0 +1,445 @@
|
|||
import {
|
||||
Translation,
|
||||
CommonTranslation,
|
||||
BookSmithViewTranslation,
|
||||
ToolViewTranslation,
|
||||
SettingsTranslation,
|
||||
ModalTranslation,
|
||||
ManagerTranslation,
|
||||
ToolbarModalTranslation,
|
||||
ComponentTranslation
|
||||
} from '../interfaces';
|
||||
|
||||
// 通用翻译
|
||||
const commonTranslation: CommonTranslation = {
|
||||
PLUGIN_NAME: 'BookSmith',
|
||||
SETTINGS: 'Settings',
|
||||
SAVE: 'Save',
|
||||
CANCEL: 'Cancel',
|
||||
HIDE: 'Hide',
|
||||
SHOW: 'Show'
|
||||
};
|
||||
|
||||
// 书籍管理视图翻译
|
||||
const bookSmithViewTranslation: BookSmithViewTranslation = {
|
||||
// 主界面
|
||||
BOOK_MANAGER: 'Book Manager',
|
||||
NEW_BOOK: 'New Book',
|
||||
SWITCH_BOOK: 'Switch Book',
|
||||
MANAGE_BOOK: 'Manage Book',
|
||||
|
||||
// 书籍相关
|
||||
BOOK_TITLE: 'Title',
|
||||
BOOK_AUTHOR: 'Author',
|
||||
BOOK_DESCRIPTION: 'Description',
|
||||
BOOK_TAGS: 'Tags',
|
||||
BOOK_COVER: 'Cover',
|
||||
|
||||
// 章节相关
|
||||
CHAPTER: 'Chapter',
|
||||
ADD_CHAPTER: 'Add Chapter',
|
||||
DELETE_CHAPTER: 'Delete Chapter',
|
||||
RENAME_CHAPTER: 'Rename Chapter',
|
||||
|
||||
// 统计相关
|
||||
STATS: 'Statistics',
|
||||
WORD_COUNT: 'Word Count',
|
||||
CHAPTER_COUNT: 'Chapter Count',
|
||||
|
||||
// 帮助提示
|
||||
HELP_TOOLTIP: `👋 Welcome to BookSmith
|
||||
|
||||
🚀 Getting Started
|
||||
• Open the Writing Toolbox on the right to activate writing assistance
|
||||
• Access Focus Mode, Statistics, Reference Management with one click
|
||||
|
||||
📚 Book Management
|
||||
• Create: Choose templates to create book projects
|
||||
• Switch: Freely switch between different works
|
||||
• Manage: Import and edit your collection
|
||||
• Templates: Customize your writing framework
|
||||
|
||||
📑 Chapter Organization
|
||||
• Tree Structure: Visualize your book hierarchy
|
||||
• Drag & Drop: Flexibly adjust chapter order
|
||||
• Status Markers: Track writing progress
|
||||
• Context Menu: Convenient chapter operations
|
||||
|
||||
⚡️ Writing Assistant
|
||||
• Real-time Stats: Word count and progress updates
|
||||
• Data Analysis: In-depth writing habit statistics
|
||||
• Focus Mode: Improve writing efficiency
|
||||
|
||||
💡 Tips
|
||||
• Support for custom writing templates
|
||||
• Quickly adjust chapters via drag & drop
|
||||
• Right-click for more operations
|
||||
|
||||
✨ May BookSmith bring joy to your creative journey.
|
||||
|
||||
💝 Support
|
||||
If BookSmith helps you, please consider supporting
|
||||
the developer via the "Donate" button in the Writing Toolbox.`,
|
||||
|
||||
// 通知消息
|
||||
SWITCHED_TO_BOOK: 'Switched to "{title}"',
|
||||
IMPORTED_AND_SWITCHED: 'Imported and switched to new book',
|
||||
CURRENT_BOOK_DELETED: 'Current book has been deleted',
|
||||
NO_BOOKS_TO_SWITCH: 'No books available to switch',
|
||||
|
||||
// 统计文本
|
||||
TODAY_WORDS: 'Today',
|
||||
TOTAL_WORDS: 'Total Words',
|
||||
CHAPTER_COMPLETION: 'Completion',
|
||||
WRITING_DAYS: 'Writing Days',
|
||||
AVERAGE_DAILY_WORDS: 'Daily Average',
|
||||
WORD_UNIT: ' words',
|
||||
DAY_UNIT: ' days',
|
||||
TEN_THOUSAND: 'K',
|
||||
|
||||
// 空状态提示
|
||||
WELCOME_MESSAGE: '👋 Welcome to BookSmith',
|
||||
EMPTY_STATE_HINT: 'Click "New Book" above to create a work, or use the "Switch" button to select an existing book'
|
||||
};
|
||||
|
||||
// 工具箱视图翻译
|
||||
const toolViewTranslation: ToolViewTranslation = {
|
||||
WRITING_TOOLBOX: 'Writing Toolbox',
|
||||
|
||||
// 写作助手
|
||||
WRITING_ASSISTANT: 'Writing Assistant',
|
||||
FOCUS_MODE: 'Focus Mode',
|
||||
CREATIVE_INSPIRATION: 'Creative Inspiration',
|
||||
CHARACTER_PROFILES: 'Character Profiles',
|
||||
WORLD_BUILDING: 'World Building',
|
||||
|
||||
// 导出发布
|
||||
EXPORT_PUBLISH: 'Export & Publish',
|
||||
DESIGN_TYPOGRAPHY: 'Design & Typography',
|
||||
GENERATE_EBOOK: 'Generate E-book',
|
||||
MORE_FEATURES: 'More Features...',
|
||||
MORE_FEATURES_MESSAGE: 'More features coming with your participation',
|
||||
|
||||
// 写作圈子
|
||||
WRITING_COMMUNITY: 'Writing Community',
|
||||
CREATIVE_COMMUNITY: 'Creative Community',
|
||||
CONTACT_AUTHOR: 'Contact Author',
|
||||
DONATE_SUPPORT: 'Donate & Support',
|
||||
|
||||
// 面板设置
|
||||
PANEL_SETTINGS: 'Panel Settings',
|
||||
FEATURE_COMING_SOON: '{feature} feature coming soon'
|
||||
};
|
||||
|
||||
// 设置面板翻译
|
||||
const settingsTranslation: SettingsTranslation = {
|
||||
SETTINGS_TITLE: 'Book Smith Settings',
|
||||
BASIC_SETTINGS: 'Basic Settings',
|
||||
TEMPLATE_SETTINGS: 'Template Settings',
|
||||
WRITING_TOOLS_SETTINGS: 'Writing Tools Settings',
|
||||
|
||||
// Basic Settings
|
||||
LANGUAGE_SETTING: 'Language',
|
||||
LANGUAGE_DESC: 'Choose plugin language / 选择插件界面语言',
|
||||
DEFAULT_AUTHOR: 'Default Author',
|
||||
DEFAULT_AUTHOR_DESC: 'Default author name for new books',
|
||||
DEFAULT_AUTHOR_PLACEHOLDER: 'Enter default author name',
|
||||
BOOK_STORAGE_PATH: 'Book Storage Path',
|
||||
BOOK_STORAGE_DESC: 'Default storage path for new books',
|
||||
STORAGE_PATH_CHANGED: 'Storage path changed, please restart Obsidian or reload for changes to take effect',
|
||||
|
||||
// Template Settings
|
||||
DEFAULT_TEMPLATE: 'Default Template',
|
||||
DEFAULT_TEMPLATE_DESC: 'Default template used when creating new books',
|
||||
BOOK_TEMPLATES: 'Book Templates',
|
||||
ADD_NEW_TEMPLATE: 'Add New Template',
|
||||
EDIT_TEMPLATE: 'Edit Template',
|
||||
DELETE_TEMPLATE: 'Delete Template',
|
||||
DELETE_TEMPLATE_TITLE: 'Delete Template',
|
||||
DELETE_TEMPLATE_DESC: 'Are you sure you want to delete this template? This action cannot be undone.',
|
||||
|
||||
// Writing Tools Settings
|
||||
FOCUS_MODE_SETTINGS: 'Focus Mode Settings',
|
||||
FOCUS_DURATION: 'Focus Duration',
|
||||
FOCUS_DURATION_DESC: 'Work duration for each focus session (minutes)',
|
||||
BREAK_DURATION: 'Break Duration',
|
||||
BREAK_DURATION_DESC: 'Break duration after each focus session (minutes)',
|
||||
WORD_GOAL: 'Word Goal',
|
||||
WORD_GOAL_DESC: 'Target word count for each focus session'
|
||||
};
|
||||
|
||||
// 模态框翻译
|
||||
const modalTranslation: ModalTranslation = {
|
||||
// Common
|
||||
COVER: 'Cover',
|
||||
COVER_DESC: 'Select cover image (optional)',
|
||||
BOOK_TITLE: 'Title',
|
||||
BOOK_TITLE_DESC: 'Please enter book title',
|
||||
BOOK_TITLE_PLACEHOLDER: 'Book title',
|
||||
SUBTITLE: 'Subtitle',
|
||||
SUBTITLE_DESC: 'Optional',
|
||||
SUBTITLE_PLACEHOLDER: 'Subtitle',
|
||||
TARGET_WORDS: 'Target Word Count',
|
||||
TARGET_WORDS_DESC: 'Set estimated total word count (in 10k)',
|
||||
TARGET_WORDS_PLACEHOLDER: 'e.g., 20 or 20.0',
|
||||
AUTHOR: 'Author',
|
||||
AUTHOR_DESC: 'Enter author names, separate multiple authors with commas',
|
||||
AUTHOR_PLACEHOLDER: 'Author',
|
||||
DESCRIPTION: 'Description',
|
||||
DESCRIPTION_DESC: 'Please enter book description',
|
||||
DESCRIPTION_PLACEHOLDER: 'Book description',
|
||||
REQUIRED_FIELDS: 'Please fill in required fields',
|
||||
|
||||
// CreateBookModal
|
||||
CREATE_BOOK_TITLE: 'Create New Book',
|
||||
BOOK_TEMPLATE: 'Template',
|
||||
TEMPLATE_CHECK_DESC: 'Please select a book template',
|
||||
SELECT_IMAGE: 'Select Image',
|
||||
COVER_UPLOAD_SUCCESS: 'Cover uploaded successfully',
|
||||
COVER_UPLOAD_FAILED: 'Cover upload failed: ',
|
||||
CREATE: 'Create',
|
||||
CREATE_SUCCESS: 'Book created successfully',
|
||||
CREATE_FAILED: 'Creation failed: ',
|
||||
|
||||
// EditBookModal
|
||||
EDIT_BOOK_TITLE: 'Edit Book',
|
||||
CHANGE_COVER: 'Change Cover',
|
||||
SELECT_COVER: 'Select Cover',
|
||||
COVER_UPDATE_SUCCESS: 'Cover updated successfully',
|
||||
COVER_UPDATE_FAILED: 'Failed to update cover: ',
|
||||
SAVE: 'Save',
|
||||
SAVE_SUCCESS: 'Saved successfully',
|
||||
SAVE_FAILED: 'Save failed: ',
|
||||
|
||||
// ManageBooksModal
|
||||
MANAGE_BOOKS_TITLE: 'Manage Books',
|
||||
SEARCH_BOOKS_PLACEHOLDER: 'Search books...',
|
||||
IMPORT_BOOK: 'Import Book',
|
||||
BOOK_AUTHOR_PREFIX: 'Author: ',
|
||||
BOOK_DESC_PREFIX: '\nDescription: ',
|
||||
BOOK_PROGRESS_PREFIX: '\nProgress: ',
|
||||
DELETE_BOOK: 'Delete',
|
||||
EDIT_BOOK: 'Edit',
|
||||
DELETE_BOOK_TITLE: 'Delete Book',
|
||||
DELETE_BOOK_DESC: 'Are you sure you want to delete "{title}"?\nThis action cannot be undone.',
|
||||
DELETE_SUCCESS: 'Deleted successfully',
|
||||
DELETE_FAILED: 'Delete failed: ',
|
||||
BOOKS_ROOT_NOT_FOUND: 'Books root directory does not exist or is inaccessible',
|
||||
NO_UNIMPORTED_BOOKS: 'No unimported book directories found',
|
||||
DETECT_UNIMPORTED_FAILED: 'Failed to detect unimported books: ',
|
||||
IMPORT_SUCCESS: 'Successfully imported book "{title}"',
|
||||
IMPORT_FAILED: 'Failed to create book configuration: ',
|
||||
|
||||
// SwitchBookModal
|
||||
SWITCH_BOOK_TITLE: 'Switch Book',
|
||||
SEARCH_BOOK_PLACEHOLDER: 'Search books...',
|
||||
BOOK_AUTHOR_LABEL: 'Author',
|
||||
BOOK_PROGRESS_LABEL: 'Progress',
|
||||
BOOK_WORDCOUNT_LABEL: 'Words',
|
||||
BOOK_LASTMOD_LABEL: 'Last Modified',
|
||||
SELECT_BOOK: 'Select',
|
||||
|
||||
// UnimportedBooksModal
|
||||
UNIMPORTED_BOOKS_TITLE: 'Select Book Directory to Import',
|
||||
NO_UNIMPORTED_FOLDERS: 'No unimported book directories found',
|
||||
CLOSE: 'Close',
|
||||
SEARCH_FOLDERS_PLACEHOLDER: 'Search directories...',
|
||||
NO_MATCHING_FOLDERS: 'No matching directories',
|
||||
IMPORT: 'Import',
|
||||
SELECT_FOLDER_FIRST: 'Please select a directory first',
|
||||
|
||||
// ReferenceModal
|
||||
REFERENCE_MODAL_TITLE: 'Add Reference',
|
||||
REFERENCE_CONTENT: 'Reference Content',
|
||||
REFERENCE_CONTENT_DESC: 'Please enter the details of the reference',
|
||||
|
||||
// NamePromptModal
|
||||
NAME_LABEL: 'Name',
|
||||
CONFIRM: 'Confirm',
|
||||
CANCEL: 'Cancel',
|
||||
|
||||
// TemplateEditModal
|
||||
TEMPLATE_EDIT_TITLE: 'Edit Template',
|
||||
TEMPLATE_CREATE_TITLE: 'Create Template',
|
||||
TEMPLATE_NAME: 'Template Name',
|
||||
TEMPLATE_NAME_PLACEHOLDER: 'Enter template name',
|
||||
TEMPLATE_DESC: 'Template Description',
|
||||
TEMPLATE_DESC_PLACEHOLDER: 'Describe the template structure',
|
||||
TEMPLATE_STRUCTURE: 'Chapter Tree',
|
||||
ADD_FILE: 'Add File',
|
||||
ADD_FOLDER: 'Add Folder',
|
||||
DELETE_FOLDER_CONFIRM: 'Are you sure you want to delete this folder and all its contents?',
|
||||
NEW_CHAPTER: 'New Chapter',
|
||||
NEW_DIRECTORY: 'New Directory',
|
||||
ENTER_NAME_PLACEHOLDER: 'Enter name',
|
||||
TEMPLATE_NAME_REQUIRED: 'Please enter template name',
|
||||
TEMPLATE_NODE_REQUIRED: 'Please add at least one node',
|
||||
TEMPLATE_NAME_EXISTS: 'Template with this name already exists',
|
||||
TEMPLATE_SAVE_SUCCESS: 'Template saved successfully',
|
||||
TEMPLATE_SAVE_FAILED: 'Failed to save template, check console for details'
|
||||
};
|
||||
const managerTranslation: ManagerTranslation = {
|
||||
// BookManager related
|
||||
BOOK_EXISTS: 'Book already exists',
|
||||
BOOK_NOT_FOUND: 'Book not found',
|
||||
BOOK_FOLDER_NOT_FOUND: 'Book folder not found',
|
||||
SAVE_CONFIG_FAILED: 'Failed to save configuration file',
|
||||
IMPORT_BOOK_FAILED: 'Failed to import book',
|
||||
UNKNOWN_AUTHOR: 'Unknown Author',
|
||||
|
||||
// TemplateManager related
|
||||
TEMPLATE_TYPE_NOT_FOUND: 'Template "{type}" does not exist',
|
||||
TEMPLATE_EXISTS: 'Template already exists',
|
||||
TEMPLATE_SAVE_FAILED: 'Failed to save template',
|
||||
|
||||
// FileManager related
|
||||
CREATE_FOLDER_FAILED: 'Failed to create folder',
|
||||
CREATE_FILE_FAILED: 'Failed to create file',
|
||||
READ_FILE_FAILED: 'Failed to read file',
|
||||
WRITE_FILE_FAILED: 'Failed to write file',
|
||||
|
||||
// FocusManager related
|
||||
BREAK_TIME_START: 'Break time started',
|
||||
FOCUS_SUMMARY: 'Focus completed!\nDuration: {duration} minutes\nInterruptions: {interruptions}\nWords written: {words}',
|
||||
|
||||
// ReferenceManager 相关
|
||||
REFERENCE_FILE_NAME: 'references.md',
|
||||
REFERENCE_FILE_NOT_FOUND: 'Please create "references.md" file in the book directory first',
|
||||
REFERENCE_FILE_ERROR: 'Reference file does not exist or type error',
|
||||
SELECT_TEXT_TO_REFERENCE: 'Please select text to reference',
|
||||
CHAPTER_INFO_ERROR: 'Unable to get current chapter information',
|
||||
|
||||
// ReferenceManager 菜单项
|
||||
EDIT_REFERENCE: 'Edit Reference',
|
||||
DELETE_REFERENCE: 'Delete Reference',
|
||||
INSERT_REFERENCE: 'Insert Reference'
|
||||
};
|
||||
const toolbarModalTranslation: ToolbarModalTranslation = {
|
||||
// CommunityModal
|
||||
COMMUNITY_TITLE: 'Writing Community',
|
||||
COMMUNITY_INTRO: 'Join the Billion Writes community to connect with other writers, get inspiration and feedback.',
|
||||
FEATURE_SHARE_TITLE: 'Share Works',
|
||||
FEATURE_SHARE_DESC: 'Share your creations and get reader feedback',
|
||||
FEATURE_DISCUSS_TITLE: 'Writing Exchange',
|
||||
FEATURE_DISCUSS_DESC: 'Discuss writing techniques with other creators',
|
||||
FEATURE_CHALLENGE_TITLE: 'Writing Challenges',
|
||||
FEATURE_CHALLENGE_DESC: 'Participate in community writing challenges to improve your skills',
|
||||
JOIN_SECTION_TITLE: 'How to Join',
|
||||
JOIN_SECTION_DESC: 'Copy and search the official account below',
|
||||
OFFICIAL_ACCOUNT: 'Official Account: BilionWrites',
|
||||
COPY_ACCOUNT: 'Copy Account',
|
||||
|
||||
// ContactModal
|
||||
ABOUT_AUTHOR: 'About Author',
|
||||
AUTHOR_INTRO_1: 'Hello, I\'m Yeban, a full-time writer and independent developer.',
|
||||
AUTHOR_INTRO_2_1: 'This plugin is ',
|
||||
AUTHOR_INTRO_2_2: 'a tool I developed to help authors write long-form content in Obsidian',
|
||||
AUTHOR_INTRO_2_3: ', hoping to ',
|
||||
AUTHOR_INTRO_2_4: 'make your writing process smoother and more enjoyable',
|
||||
AUTHOR_INTRO_2_5: '.',
|
||||
AUTHOR_INTRO_3: 'If this plugin helps with your writing, or if you\'d like to support my independent development and creation, feel free to buy me a coffee ☕.',
|
||||
AUTHOR_INTRO_4_1: 'Your support means a lot',
|
||||
AUTHOR_INTRO_4_2: ', it allows me to focus on developing more useful tools to aid your creative journey.',
|
||||
DONATE_TEXT: 'Support the author:',
|
||||
DONATE_BUTTON: 'Support',
|
||||
MORE_INFO_TEXT: 'If you want to learn more about writing, creative techniques, or follow my future work updates, welcome to follow my social media.',
|
||||
CONTACT_TITLE: 'Contact:',
|
||||
CONTACT_WECHAT_OFFICIAL: 'WeChat Official',
|
||||
CONTACT_XIAOHONGSHU: 'Xiaohongshu',
|
||||
CONTACT_WECHAT: 'WeChat',
|
||||
CONTACT_GITHUB: 'GitHub',
|
||||
COPY_SUCCESS: '{type} copied to clipboard',
|
||||
COPY_FAILED: 'Copy failed, please copy manually: {value}',
|
||||
|
||||
// DonateModal
|
||||
DONATE_MODAL_TITLE: 'Support Me',
|
||||
COMMUNITY_STATS_TITLE: 'Community Stats',
|
||||
COMMUNITY_STATS_USERS: '1200+ users, 32 supporters',
|
||||
COMMUNITY_STATS_WORDS: '5000+ words written daily',
|
||||
|
||||
DONATE_AMOUNT_COFFEE: 'Buy me a coffee',
|
||||
DONATE_AMOUNT_CHAPTER: 'Chapter sponsor',
|
||||
DONATE_AMOUNT_FEATURE: 'Feature builder',
|
||||
DONATE_FEEDBACK_COFFEE: 'Thanks for your coffee support!',
|
||||
DONATE_FEEDBACK_CHAPTER: 'You get one vote for new features',
|
||||
DONATE_FEEDBACK_FEATURE: 'Join our beta testing group, contact via WeChat Official Account',
|
||||
|
||||
PAYMENT_WECHAT: 'WeChat Pay',
|
||||
PAYMENT_ALIPAY: 'Alipay',
|
||||
PAYMENT_KOFI: 'Ko-fi',
|
||||
CURRENCY_UNIT: 'CNY'
|
||||
};
|
||||
const componentTranslation: ComponentTranslation = {
|
||||
// ChapterTree
|
||||
NEW_FILE: 'New File',
|
||||
NEW_FOLDER: 'New Folder',
|
||||
ENTER_FILE_NAME: 'Enter file name',
|
||||
ENTER_FOLDER_NAME: 'Enter folder name',
|
||||
OPEN_IN_NEW_TAB: 'Open in New Tab',
|
||||
OPEN_IN_NEW_PANE: 'Open in New Pane',
|
||||
MARK_AS_COMPLETE: 'Mark as Complete',
|
||||
MARK_AS_DRAFT: 'Mark as Draft',
|
||||
EXCLUDE_FROM_STATS: 'Exclude from Stats',
|
||||
INCLUDE_IN_STATS: 'Include in Stats',
|
||||
CREATE_COPY: 'Create Copy',
|
||||
COPY_NAME: '{name} Copy',
|
||||
RENAME: 'Rename',
|
||||
DELETE: 'Delete',
|
||||
DELETE_FILE_TITLE: 'Delete File',
|
||||
DELETE_FILE_DESC: 'Are you sure you want to delete "{title}"?\nIt will be moved to system trash.',
|
||||
DELETE_FOLDER_TITLE: 'Delete Folder',
|
||||
DELETE_FOLDER_DESC: 'Are you sure you want to delete folder "{title}" and all its contents?\nThey will be moved to system trash.',
|
||||
COPY_SUCCESS: 'Copy created successfully',
|
||||
COPY_FAILED: 'Failed to create copy: {error}',
|
||||
RENAME_SUCCESS: 'Renamed successfully',
|
||||
RENAME_FAILED: 'Failed to rename: {error}',
|
||||
DELETE_SUCCESS: 'Deleted successfully',
|
||||
DELETE_FAILED: 'Failed to delete: {error}',
|
||||
MOVE_FAILED: 'Failed to move',
|
||||
SOURCE_NOT_FOUND: 'Source file not found',
|
||||
TARGET_EXISTS: 'File with same name exists at target location',
|
||||
TARGET_FOLDER_NOT_FOUND: 'Target folder not found',
|
||||
EXCLUDED_NOTICE: '"{title}" has been excluded',
|
||||
INCLUDED_NOTICE: '"{title}" has been included',
|
||||
ENTER_NEW_NAME: 'Enter new name',
|
||||
|
||||
// FocusToolView
|
||||
FOCUS_MODE: 'Focus Mode',
|
||||
START_FOCUS: 'Start Focus',
|
||||
EXIT: 'Exit',
|
||||
FOCUS_SESSIONS: 'Focus Sessions',
|
||||
INTERRUPTIONS: 'Interruptions',
|
||||
CURRENT_WORDS: 'Current Words',
|
||||
WORD_GOAL: 'Word Goal',
|
||||
TOTAL_FOCUS_WORDS: 'Total Focus Words',
|
||||
PAUSE: 'Pause',
|
||||
RESUME: 'Resume',
|
||||
END: 'End',
|
||||
ENCOURAGEMENT_1: '🎉 Amazing! You\'ve reached your word goal! Keep it up~',
|
||||
ENCOURAGEMENT_2: '✨ Excellent! Goal achieved! Let\'s keep moving forward!',
|
||||
ENCOURAGEMENT_3: '🌟 Perfect! Goal reached! Maintain this enthusiasm!',
|
||||
ENCOURAGEMENT_4: '🎯 Goal achieved! Your persistence is admirable!',
|
||||
ENCOURAGEMENT_5: '💪 Outstanding performance! Goal completed! Keep going!',
|
||||
EXIT_FOCUS: 'Exit Focus',
|
||||
EXIT_FOCUS_DESC: 'Are you sure you want to exit? Current focus progress will be lost.',
|
||||
END_FOCUS: 'End Focus',
|
||||
END_FOCUS_DESC: 'Are you sure you want to end focus? This will count as an interruption.',
|
||||
FOCUSING: 'Focusing',
|
||||
PAUSED: 'Paused',
|
||||
BREAK_TIME: 'Break Time',
|
||||
READY_TO_START: 'Ready to Start'
|
||||
};
|
||||
// 合并所有翻译
|
||||
const translation: Translation = {
|
||||
...commonTranslation,
|
||||
...bookSmithViewTranslation,
|
||||
...toolViewTranslation,
|
||||
...settingsTranslation,
|
||||
...modalTranslation,
|
||||
...managerTranslation,
|
||||
...toolbarModalTranslation,
|
||||
...componentTranslation
|
||||
};
|
||||
|
||||
|
||||
export default translation;
|
||||
449
src/i18n/locales/zh-CN.ts
Normal file
449
src/i18n/locales/zh-CN.ts
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
import {
|
||||
Translation,
|
||||
CommonTranslation,
|
||||
BookSmithViewTranslation,
|
||||
ToolViewTranslation,
|
||||
SettingsTranslation,
|
||||
ModalTranslation,
|
||||
ManagerTranslation,
|
||||
ToolbarModalTranslation,
|
||||
ComponentTranslation
|
||||
} from '../interfaces';
|
||||
|
||||
// 通用翻译
|
||||
const commonTranslation: CommonTranslation = {
|
||||
PLUGIN_NAME: 'BookSmith 书籍创作',
|
||||
SETTINGS: '设置',
|
||||
SAVE: '保存',
|
||||
CANCEL: '取消',
|
||||
HIDE: '隐藏',
|
||||
SHOW: '显示'
|
||||
};
|
||||
|
||||
// 书籍管理视图翻译
|
||||
const bookSmithViewTranslation: BookSmithViewTranslation = {
|
||||
// 主界面
|
||||
BOOK_MANAGER: '书籍管理',
|
||||
NEW_BOOK: '新建书籍',
|
||||
SWITCH_BOOK: '切换书籍',
|
||||
MANAGE_BOOK: '管理书籍',
|
||||
|
||||
// 书籍相关
|
||||
BOOK_TITLE: '书名',
|
||||
BOOK_AUTHOR: '作者',
|
||||
BOOK_DESCRIPTION: '简介',
|
||||
BOOK_TAGS: '标签',
|
||||
BOOK_COVER: '封面',
|
||||
|
||||
// 章节相关
|
||||
CHAPTER: '章节',
|
||||
ADD_CHAPTER: '添加章节',
|
||||
DELETE_CHAPTER: '删除章节',
|
||||
RENAME_CHAPTER: '重命名章节',
|
||||
|
||||
// 统计相关
|
||||
STATS: '统计',
|
||||
WORD_COUNT: '字数',
|
||||
CHAPTER_COUNT: '章节数',
|
||||
|
||||
// 帮助提示
|
||||
HELP_TOOLTIP: `👋 欢迎使用 BookSmith
|
||||
|
||||
🚀 开始使用
|
||||
• 打开右侧【写作工具箱】,激活创作辅助功能
|
||||
• 专注模式、统计分析、引用管理等工具一键可得
|
||||
|
||||
📚 创作管理
|
||||
• 新建:选择模板创建书籍项目
|
||||
• 切换:在不同作品间自由切换
|
||||
• 管理:导入、编辑您的作品集
|
||||
• 模板:自定义专属写作框架
|
||||
|
||||
📑 章节编排
|
||||
• 树形结构:直观展现层次结构
|
||||
• 拖拽排序:灵活调整章节顺序
|
||||
• 状态标记:追踪创作进度
|
||||
• 右键菜单:便捷的章节操作
|
||||
|
||||
⚡️ 创作助手
|
||||
• 实时统计:字数、进度实时更新
|
||||
• 数据分析:写作习惯深度统计
|
||||
• 专注模式:提升写作效率
|
||||
|
||||
💡 小贴士
|
||||
• 支持自定义多种写作模板
|
||||
• 可通过拖拽快速调整章节
|
||||
• 右键点击可进行更多操作
|
||||
|
||||
✨ 愿 BookSmith 能让您享受创作的美好时光。
|
||||
|
||||
💝 赞赏支持
|
||||
如果 BookSmith 为您带来帮助,请前往
|
||||
右侧写作工具箱【赞赏捐赠】,支持我继续创作优雅工具。`,
|
||||
|
||||
// 通知消息
|
||||
SWITCHED_TO_BOOK: '已切换到《{title}》',
|
||||
IMPORTED_AND_SWITCHED: '已导入并切换到新书籍',
|
||||
CURRENT_BOOK_DELETED: '当前书籍已被删除',
|
||||
NO_BOOKS_TO_SWITCH: '暂无可切换的书籍',
|
||||
|
||||
// 统计文本
|
||||
TODAY_WORDS: '今日字数',
|
||||
TOTAL_WORDS: '字数统计',
|
||||
CHAPTER_COMPLETION: '章节完成',
|
||||
WRITING_DAYS: '写作天数',
|
||||
AVERAGE_DAILY_WORDS: '日均字数',
|
||||
WORD_UNIT: '字',
|
||||
DAY_UNIT: '天',
|
||||
TEN_THOUSAND: '万',
|
||||
|
||||
// 空状态提示
|
||||
WELCOME_MESSAGE: '👋 欢迎使用 BookSmith',
|
||||
EMPTY_STATE_HINT: '点击上方的"新建书籍"创建作品,或使用"切换"按钮选择已有书籍'
|
||||
};
|
||||
|
||||
// 工具箱视图翻译
|
||||
const toolViewTranslation: ToolViewTranslation = {
|
||||
WRITING_TOOLBOX: '写作工具箱',
|
||||
|
||||
// 写作助手
|
||||
WRITING_ASSISTANT: '写作助手',
|
||||
FOCUS_MODE: '专注模式',
|
||||
CREATIVE_INSPIRATION: '创作灵感',
|
||||
CHARACTER_PROFILES: '人物档案',
|
||||
WORLD_BUILDING: '世界构建',
|
||||
|
||||
// 导出发布
|
||||
EXPORT_PUBLISH: '导出发布',
|
||||
DESIGN_TYPOGRAPHY: '设计排版',
|
||||
GENERATE_EBOOK: '生成电子书',
|
||||
MORE_FEATURES: '更多功能...',
|
||||
MORE_FEATURES_MESSAGE: '更多功能等你一起共创',
|
||||
|
||||
// 写作圈子
|
||||
WRITING_COMMUNITY: '写作圈子',
|
||||
CREATIVE_COMMUNITY: '创作社区',
|
||||
CONTACT_AUTHOR: '联系作者',
|
||||
DONATE_SUPPORT: '赞助捐赠',
|
||||
|
||||
// 面板设置
|
||||
PANEL_SETTINGS: '面板设置',
|
||||
FEATURE_COMING_SOON: '{feature}功能即将上线'
|
||||
};
|
||||
|
||||
// 设置面板翻译
|
||||
const settingsTranslation: SettingsTranslation = {
|
||||
|
||||
SETTINGS_TITLE: 'Book Smith 设置',
|
||||
BASIC_SETTINGS: '基本设置',
|
||||
TEMPLATE_SETTINGS: '模板设置',
|
||||
WRITING_TOOLS_SETTINGS: '写作工具箱设置',
|
||||
|
||||
// 基本设置
|
||||
LANGUAGE_SETTING: '语言',
|
||||
LANGUAGE_DESC: '选择插件界面语言 / Choose plugin language',
|
||||
DEFAULT_AUTHOR: '默认作者',
|
||||
DEFAULT_AUTHOR_DESC: '创建新书籍时的默认作者名',
|
||||
DEFAULT_AUTHOR_PLACEHOLDER: '输入默认作者名',
|
||||
BOOK_STORAGE_PATH: '书籍存储路径',
|
||||
BOOK_STORAGE_DESC: '新建书籍的默认存储路径',
|
||||
STORAGE_PATH_CHANGED: '存储路径已更改,请重启 Obsidian 或重新加载以使更改生效',
|
||||
|
||||
// 模板设置
|
||||
DEFAULT_TEMPLATE: '默认模板',
|
||||
DEFAULT_TEMPLATE_DESC: '创建新书籍时使用的默认模板',
|
||||
BOOK_TEMPLATES: '书籍模板',
|
||||
ADD_NEW_TEMPLATE: '添加新模板',
|
||||
EDIT_TEMPLATE: '编辑模板',
|
||||
DELETE_TEMPLATE: '删除模板',
|
||||
DELETE_TEMPLATE_TITLE: '删除模板',
|
||||
DELETE_TEMPLATE_DESC: '确定要删除此模板吗?删除后无法恢复。',
|
||||
|
||||
// 写作工具箱设置
|
||||
FOCUS_MODE_SETTINGS: '专注模式设置',
|
||||
FOCUS_DURATION: '专注时长',
|
||||
FOCUS_DURATION_DESC: '每个专注周期的工作时长(分钟)',
|
||||
BREAK_DURATION: '间隔时长',
|
||||
BREAK_DURATION_DESC: '每个专注周期后的休息时长(分钟)',
|
||||
WORD_GOAL: '字数目标',
|
||||
WORD_GOAL_DESC: '每个专注周期的目标字数'
|
||||
};
|
||||
|
||||
// 模态框翻译
|
||||
const modalTranslation: ModalTranslation = {
|
||||
// 通用
|
||||
COVER: '封面',
|
||||
COVER_DESC: '选择封面图片(可选)',
|
||||
BOOK_TITLE: '书名',
|
||||
BOOK_TITLE_DESC: '请输入书籍标题',
|
||||
BOOK_TITLE_PLACEHOLDER: '书名',
|
||||
SUBTITLE: '副标题',
|
||||
SUBTITLE_DESC: '可选',
|
||||
SUBTITLE_PLACEHOLDER: '副标题',
|
||||
TARGET_WORDS: '目标字数',
|
||||
TARGET_WORDS_DESC: '设置本书预计总字数:万字',
|
||||
TARGET_WORDS_PLACEHOLDER: '例如:20或20.0万',
|
||||
AUTHOR: '作者',
|
||||
AUTHOR_DESC: '请输入作者名称,多个作者用逗号分隔',
|
||||
AUTHOR_PLACEHOLDER: '作者',
|
||||
DESCRIPTION: '简介',
|
||||
DESCRIPTION_DESC: '请输入书籍简介',
|
||||
DESCRIPTION_PLACEHOLDER: '书籍简介',
|
||||
REQUIRED_FIELDS: '请填写必要信息',
|
||||
|
||||
// CreateBookModal
|
||||
CREATE_BOOK_TITLE: '创建新书籍',
|
||||
BOOK_TEMPLATE: '模板',
|
||||
TEMPLATE_CHECK_DESC: '请选择书籍模板',
|
||||
SELECT_IMAGE: '选择图片',
|
||||
COVER_UPLOAD_SUCCESS: '封面上传成功',
|
||||
COVER_UPLOAD_FAILED: '封面上传失败:',
|
||||
CREATE: '创建',
|
||||
CREATE_SUCCESS: '书籍创建成功',
|
||||
CREATE_FAILED: '创建失败:',
|
||||
|
||||
// EditBookModal
|
||||
EDIT_BOOK_TITLE: '编辑书籍',
|
||||
CHANGE_COVER: '更换封面',
|
||||
SELECT_COVER: '选择封面',
|
||||
COVER_UPDATE_SUCCESS: '封面更新成功',
|
||||
COVER_UPDATE_FAILED: '封面更新失败:',
|
||||
SAVE: '保存',
|
||||
SAVE_SUCCESS: '保存成功',
|
||||
SAVE_FAILED: '保存失败:',
|
||||
|
||||
// ManageBooksModal
|
||||
MANAGE_BOOKS_TITLE: '管理书籍',
|
||||
SEARCH_BOOKS_PLACEHOLDER: '搜索书籍...',
|
||||
IMPORT_BOOK: '导入书籍',
|
||||
BOOK_AUTHOR_PREFIX: '作者:',
|
||||
BOOK_DESC_PREFIX: '\n简介:',
|
||||
BOOK_PROGRESS_PREFIX: '\n创作轨迹:',
|
||||
DELETE_BOOK: '删除',
|
||||
EDIT_BOOK: '编辑',
|
||||
DELETE_BOOK_TITLE: '删除书籍',
|
||||
DELETE_BOOK_DESC: '确定要删除《{title}》吗?\n此操作不可恢复。',
|
||||
DELETE_SUCCESS: '删除成功',
|
||||
DELETE_FAILED: '删除失败:',
|
||||
BOOKS_ROOT_NOT_FOUND: '书籍根目录不存在或无法访问',
|
||||
NO_UNIMPORTED_BOOKS: '没有找到未导入的书籍目录',
|
||||
DETECT_UNIMPORTED_FAILED: '检测未导入书籍失败:',
|
||||
IMPORT_SUCCESS: '成功导入书籍《{title}》',
|
||||
IMPORT_FAILED: '创建书籍配置失败:',
|
||||
|
||||
// SwitchBookModal
|
||||
SWITCH_BOOK_TITLE: '切换书籍',
|
||||
SEARCH_BOOK_PLACEHOLDER: '搜索书籍...',
|
||||
BOOK_AUTHOR_LABEL: '作者',
|
||||
BOOK_PROGRESS_LABEL: '进度',
|
||||
BOOK_WORDCOUNT_LABEL: '字数',
|
||||
BOOK_LASTMOD_LABEL: '最后修改',
|
||||
SELECT_BOOK: '选择',
|
||||
|
||||
// UnimportedBooksModal
|
||||
UNIMPORTED_BOOKS_TITLE: '选择要导入的书籍目录',
|
||||
NO_UNIMPORTED_FOLDERS: '没有找到未导入的书籍目录',
|
||||
CLOSE: '关闭',
|
||||
SEARCH_FOLDERS_PLACEHOLDER: '搜索目录...',
|
||||
NO_MATCHING_FOLDERS: '没有匹配的目录',
|
||||
IMPORT: '导入',
|
||||
SELECT_FOLDER_FIRST: '请先选择一个目录',
|
||||
|
||||
// ReferenceModal
|
||||
REFERENCE_MODAL_TITLE: '添加引用内容',
|
||||
REFERENCE_CONTENT: '引用内容',
|
||||
REFERENCE_CONTENT_DESC: '请输入引用内容的详细信息',
|
||||
|
||||
// ConfirmModal
|
||||
// NamePromptModal
|
||||
NAME_LABEL: '名称',
|
||||
CONFIRM: '确定',
|
||||
CANCEL: '取消',
|
||||
|
||||
// TemplateEditModal
|
||||
TEMPLATE_EDIT_TITLE: '编辑模板',
|
||||
TEMPLATE_CREATE_TITLE: '新建模板',
|
||||
TEMPLATE_NAME: '模板名称',
|
||||
TEMPLATE_NAME_PLACEHOLDER: '请输入模板名称',
|
||||
TEMPLATE_DESC: '模板描述',
|
||||
TEMPLATE_DESC_PLACEHOLDER: '可描述模板的简要结构',
|
||||
TEMPLATE_STRUCTURE: '章节树',
|
||||
ADD_FILE: '添加文件',
|
||||
ADD_FOLDER: '添加文件夹',
|
||||
DELETE_FOLDER_CONFIRM: '确定要删除此文件夹及其所有内容吗?',
|
||||
NEW_CHAPTER: '新章节',
|
||||
NEW_DIRECTORY: '新目录',
|
||||
ENTER_NAME_PLACEHOLDER: '输入名称',
|
||||
TEMPLATE_NAME_REQUIRED: '请输入模板名称',
|
||||
TEMPLATE_NODE_REQUIRED: '请至少添加一个节点',
|
||||
TEMPLATE_NAME_EXISTS: '已存在同名模板,请修改模板名称',
|
||||
TEMPLATE_SAVE_SUCCESS: '模板保存成功',
|
||||
TEMPLATE_SAVE_FAILED: '保存模板失败,请查看控制台了解详情'
|
||||
};
|
||||
|
||||
const managerTranslation: ManagerTranslation = {
|
||||
// BookManager 相关
|
||||
BOOK_EXISTS: '书籍已存在',
|
||||
BOOK_NOT_FOUND: '书籍不存在',
|
||||
BOOK_FOLDER_NOT_FOUND: '书籍文件夹不存在',
|
||||
SAVE_CONFIG_FAILED: '保存配置文件失败',
|
||||
IMPORT_BOOK_FAILED: '导入书籍失败',
|
||||
UNKNOWN_AUTHOR: '未知作者',
|
||||
|
||||
// TemplateManager 相关
|
||||
TEMPLATE_TYPE_NOT_FOUND: '模板 "{type}" 不存在',
|
||||
TEMPLATE_EXISTS: '模板已存在',
|
||||
TEMPLATE_SAVE_FAILED: '保存模板失败',
|
||||
|
||||
// FileManager 相关
|
||||
CREATE_FOLDER_FAILED: '创建文件夹失败',
|
||||
CREATE_FILE_FAILED: '创建文件失败',
|
||||
READ_FILE_FAILED: '读取文件失败',
|
||||
WRITE_FILE_FAILED: '写入文件失败',
|
||||
|
||||
// FocusManager 相关
|
||||
BREAK_TIME_START: '休息时间开始',
|
||||
FOCUS_SUMMARY: '专注结束!\n完成时间:{duration}分钟\n中断次数:{interruptions}次\n本次字数:{words}',
|
||||
|
||||
// ReferenceManager 相关
|
||||
REFERENCE_FILE_NAME: '引用书目.md',
|
||||
REFERENCE_FILE_NOT_FOUND: '请先在书籍目录下创建"引用书目.md"文件',
|
||||
REFERENCE_FILE_ERROR: '引用书目文件不存在或类型错误',
|
||||
SELECT_TEXT_TO_REFERENCE: '请选择要引用的文本',
|
||||
CHAPTER_INFO_ERROR: '无法获取当前章节信息',
|
||||
// ReferenceManager 菜单项
|
||||
EDIT_REFERENCE: '编辑当前引用',
|
||||
DELETE_REFERENCE: '删除当前引用',
|
||||
INSERT_REFERENCE: '插入新引用'
|
||||
};
|
||||
const toolbarModalTranslation: ToolbarModalTranslation = {
|
||||
// CommunityModal
|
||||
COMMUNITY_TITLE: '创作社区',
|
||||
COMMUNITY_INTRO: '加入亿万写作创作社区,与其他创作者交流,获取写作灵感和反馈。',
|
||||
FEATURE_SHARE_TITLE: '作品分享',
|
||||
FEATURE_SHARE_DESC: '分享你的创作,获取读者反馈',
|
||||
FEATURE_DISCUSS_TITLE: '创作交流',
|
||||
FEATURE_DISCUSS_DESC: '与其他创作者讨论写作技巧',
|
||||
FEATURE_CHALLENGE_TITLE: '写作挑战',
|
||||
FEATURE_CHALLENGE_DESC: '参与社区写作挑战,提升创作能力',
|
||||
JOIN_SECTION_TITLE: '加入方式',
|
||||
JOIN_SECTION_DESC: '复制下方公众号,搜索关注',
|
||||
OFFICIAL_ACCOUNT: '公众号:BilionWrites',
|
||||
COPY_ACCOUNT: '复制公众号',
|
||||
|
||||
// ContactModal
|
||||
ABOUT_AUTHOR: '关于作者',
|
||||
AUTHOR_INTRO_1: '你好,我是【夜半】,一名全职写作与独立开发者。',
|
||||
AUTHOR_INTRO_2_1: '这款插件是',
|
||||
AUTHOR_INTRO_2_2: '我为了帮助在Obsidian中进行长篇创作的作者而开发的工具',
|
||||
AUTHOR_INTRO_2_3: ',希望能',
|
||||
AUTHOR_INTRO_2_4: '让你的写作过程更流畅,创作体验更愉悦',
|
||||
AUTHOR_INTRO_2_5: '。',
|
||||
AUTHOR_INTRO_3: '如果这款插件对你的写作有所帮助,或者你愿意支持我的独立开发与创作,欢迎请我喝咖啡☕。',
|
||||
AUTHOR_INTRO_4_1: '你的支持意义重大',
|
||||
AUTHOR_INTRO_4_2: ',它能让我更专注地开发更多实用工具,助力你的创作之旅。',
|
||||
DONATE_TEXT: '如需支持作者:',
|
||||
DONATE_BUTTON: '赞赏支持',
|
||||
MORE_INFO_TEXT: '如果你想了解更多关于写作、创作技巧的内容,或者关注我未来的作品动态,欢迎关注我的社交媒体。',
|
||||
CONTACT_TITLE: '联系方式:',
|
||||
CONTACT_WECHAT_OFFICIAL: '公众号',
|
||||
CONTACT_XIAOHONGSHU: '小红书',
|
||||
CONTACT_WECHAT: '微信',
|
||||
CONTACT_GITHUB: 'GitHub',
|
||||
COPY_SUCCESS: '{type}已复制到剪贴板',
|
||||
COPY_FAILED: '复制失败,请手动复制:{value}',
|
||||
|
||||
// DonateModal
|
||||
DONATE_MODAL_TITLE: '笔墨有情',
|
||||
COMMUNITY_STATS_TITLE: '社区数据',
|
||||
COMMUNITY_STATS_USERS: '已有 1200+ 用户,32位支持者',
|
||||
COMMUNITY_STATS_WORDS: '平均每天创作 5000+ 字',
|
||||
|
||||
DONATE_AMOUNT_COFFEE: '暖心咖啡',
|
||||
DONATE_AMOUNT_CHAPTER: '章节赞助',
|
||||
DONATE_AMOUNT_FEATURE: '功能共建',
|
||||
DONATE_FEEDBACK_COFFEE: '感谢您的咖啡赞助!',
|
||||
DONATE_FEEDBACK_CHAPTER: '给您一次新功能投票权',
|
||||
DONATE_FEEDBACK_FEATURE: '邀请您加入内测社群,公众号链接我',
|
||||
|
||||
PAYMENT_WECHAT: '微信赞赏',
|
||||
PAYMENT_ALIPAY: '支付宝赞赏',
|
||||
PAYMENT_KOFI: 'Ko-fi 赞赏',
|
||||
CURRENCY_UNIT: '元'
|
||||
};
|
||||
|
||||
const componentTranslation: ComponentTranslation = {
|
||||
// ChapterTree
|
||||
NEW_FILE: '新建文件',
|
||||
NEW_FOLDER: '新建文件夹',
|
||||
ENTER_FILE_NAME: '请输入文件名',
|
||||
ENTER_FOLDER_NAME: '请输入文件夹名',
|
||||
OPEN_IN_NEW_TAB: '在新标签页中打开',
|
||||
OPEN_IN_NEW_PANE: '在新标签组中打开',
|
||||
MARK_AS_COMPLETE: '标记完成章节',
|
||||
MARK_AS_DRAFT: '标记重新创作',
|
||||
EXCLUDE_FROM_STATS: '排除在统计与导出外',
|
||||
INCLUDE_IN_STATS: '包含在统计与导出中',
|
||||
CREATE_COPY: '创建副本',
|
||||
COPY_NAME: '{name} 副本',
|
||||
RENAME: '重命名',
|
||||
DELETE: '删除',
|
||||
DELETE_FILE_TITLE: '删除文件',
|
||||
DELETE_FILE_DESC: '确定要删除文件 "{title}" 吗?\n它将被移动到系统回收站。',
|
||||
DELETE_FOLDER_TITLE: '删除文件夹',
|
||||
DELETE_FOLDER_DESC: '确定要删除文件夹 "{title}" 及其所有内容吗?\n它们将被移动到系统回收站。',
|
||||
COPY_SUCCESS: '创建副本成功',
|
||||
COPY_FAILED: '创建副本失败: {error}',
|
||||
RENAME_SUCCESS: '重命名成功',
|
||||
RENAME_FAILED: '重命名失败: {error}',
|
||||
DELETE_SUCCESS: '删除成功',
|
||||
DELETE_FAILED: '删除失败: {error}',
|
||||
MOVE_FAILED: '移动失败',
|
||||
SOURCE_NOT_FOUND: '源文件不存在',
|
||||
TARGET_EXISTS: '目标位置已存在同名文件',
|
||||
TARGET_FOLDER_NOT_FOUND: '目标文件夹不存在',
|
||||
EXCLUDED_NOTICE: '已将"{title}"排除',
|
||||
INCLUDED_NOTICE: '已将"{title}"包含',
|
||||
ENTER_NEW_NAME: '请输入新名称',
|
||||
|
||||
// FocusToolView
|
||||
FOCUS_MODE: '专注模式',
|
||||
START_FOCUS: '开始专注',
|
||||
EXIT: '退出',
|
||||
FOCUS_SESSIONS: '专注次数',
|
||||
INTERRUPTIONS: '中断次数',
|
||||
CURRENT_WORDS: '当前字数',
|
||||
WORD_GOAL: '目标字数',
|
||||
TOTAL_FOCUS_WORDS: '专注总字数',
|
||||
PAUSE: '暂停',
|
||||
RESUME: '继续',
|
||||
END: '结束',
|
||||
ENCOURAGEMENT_1: '🎉 太棒了!已达到目标字数!继续保持~',
|
||||
ENCOURAGEMENT_2: '✨ 厉害!目标达成!让我们继续前进!',
|
||||
ENCOURAGEMENT_3: '🌟 完美!达到目标了!保持这份热情!',
|
||||
ENCOURAGEMENT_4: '🎯 目标达成!你的坚持值得表扬!',
|
||||
ENCOURAGEMENT_5: '💪 出色的表现!目标完成!再接再厉!',
|
||||
EXIT_FOCUS: '退出专注',
|
||||
EXIT_FOCUS_DESC: '确定要退出专注吗?当前专注进度将会丢失。',
|
||||
END_FOCUS: '结束专注',
|
||||
END_FOCUS_DESC: '确定要结束专注吗?这将计入中断次数。',
|
||||
FOCUSING: '专注中',
|
||||
PAUSED: '已暂停',
|
||||
BREAK_TIME: '休息时间',
|
||||
READY_TO_START: '准备开始'
|
||||
};
|
||||
|
||||
// 合并所有翻译
|
||||
const translation: Translation = {
|
||||
...commonTranslation,
|
||||
...bookSmithViewTranslation,
|
||||
...toolViewTranslation,
|
||||
...settingsTranslation,
|
||||
...modalTranslation,
|
||||
...managerTranslation,
|
||||
...toolbarModalTranslation,
|
||||
...componentTranslation
|
||||
};
|
||||
|
||||
export default translation;
|
||||
|
||||
|
|
@ -7,6 +7,7 @@ import { activateView } from './utils/viewUtils';
|
|||
import { BookManager } from './services/BookManager';
|
||||
import { TemplateManager } from './services/TemplateManager';
|
||||
import { BookStatsManager } from './services/BookStatsManager';
|
||||
import { i18n, Locale } from './i18n/i18n';
|
||||
|
||||
export default class BookSmithPlugin extends Plugin {
|
||||
settings: BookSmithSettings;
|
||||
|
|
@ -16,6 +17,7 @@ export default class BookSmithPlugin extends Plugin {
|
|||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
i18n.setLocale(this.settings.language);
|
||||
|
||||
// 初始化所有管理器
|
||||
this.bookManager = new BookManager(this.app, this.settings);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { BaseModal } from "./BaseModal";
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class CommunityModal extends BaseModal {
|
||||
constructor(container: HTMLElement) {
|
||||
super(container, '创作社区');
|
||||
super(container, i18n.t('COMMUNITY_TITLE'));
|
||||
}
|
||||
|
||||
protected createContent() {
|
||||
|
|
@ -10,34 +11,30 @@ export class CommunityModal extends BaseModal {
|
|||
|
||||
// 社区介绍
|
||||
const intro = content.createDiv({ cls: 'community-intro' });
|
||||
intro.createEl('p', {
|
||||
text: '加入亿万写作创作社区,与其他创作者交流,获取写作灵感和反馈。'
|
||||
});
|
||||
intro.createEl('p', { text: i18n.t('COMMUNITY_INTRO') });
|
||||
|
||||
// 社区功能卡片
|
||||
const features = content.createDiv({ cls: 'community-features' });
|
||||
|
||||
this.createFeatureCard(features, '📝', '作品分享', '分享你的创作,获取读者反馈');
|
||||
this.createFeatureCard(features, '👥', '创作交流', '与其他创作者讨论写作技巧');
|
||||
this.createFeatureCard(features, '🏆', '写作挑战', '参与社区写作挑战,提升创作能力');
|
||||
this.createFeatureCard(features, '📝', i18n.t('FEATURE_SHARE_TITLE'), i18n.t('FEATURE_SHARE_DESC'));
|
||||
this.createFeatureCard(features, '👥', i18n.t('FEATURE_DISCUSS_TITLE'), i18n.t('FEATURE_DISCUSS_DESC'));
|
||||
this.createFeatureCard(features, '🏆', i18n.t('FEATURE_CHALLENGE_TITLE'), i18n.t('FEATURE_CHALLENGE_DESC'));
|
||||
|
||||
// 加入方式
|
||||
const joinSection = content.createDiv({ cls: 'join-section' });
|
||||
joinSection.createEl('h3', { text: '加入方式' });
|
||||
joinSection.createEl('h3', { text: i18n.t('JOIN_SECTION_TITLE') });
|
||||
|
||||
const joinInfo = joinSection.createDiv({ cls: 'join-info' });
|
||||
joinInfo.createEl('p', { text: '复制下方公众号,搜索关注' });
|
||||
|
||||
|
||||
joinInfo.createEl('p', { text: '公众号:BilionWrites', cls: 'account-name' });
|
||||
joinInfo.createEl('p', { text: i18n.t('JOIN_SECTION_DESC') });
|
||||
joinInfo.createEl('p', { text: i18n.t('OFFICIAL_ACCOUNT'), cls: 'account-name' });
|
||||
|
||||
// 加入按钮
|
||||
const joinBtn = content.createDiv({ cls: 'join-btn', text: '复制公众号' });
|
||||
const joinBtn = content.createDiv({ cls: 'join-btn', text: i18n.t('COPY_ACCOUNT') });
|
||||
joinBtn.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText('BilionWrites').then(() => {
|
||||
this.showNotice('公众号已复制到剪贴板');
|
||||
this.showNotice(i18n.t('COPY_SUCCESS', { type: i18n.t('OFFICIAL_ACCOUNT') }));
|
||||
}).catch(() => {
|
||||
this.showNotice('复制失败,请手动复制:BilionWrites');
|
||||
this.showNotice(i18n.t('COPY_FAILED', { value: 'BilionWrites' }));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { App, Modal } from 'obsidian';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class ConfirmModal extends Modal {
|
||||
private confirmed = false;
|
||||
|
|
@ -25,13 +26,13 @@ export class ConfirmModal extends Modal {
|
|||
const buttonContainer = contentEl.createDiv({ cls: 'modal-button-container' });
|
||||
|
||||
// 取消按钮
|
||||
buttonContainer.createEl('button', { text: '取消' })
|
||||
buttonContainer.createEl('button', { text: i18n.t('CANCEL') })
|
||||
.addEventListener('click', () => this.close());
|
||||
|
||||
// 确认按钮
|
||||
const confirmButton = buttonContainer.createEl('button', {
|
||||
cls: 'mod-cta',
|
||||
text: '确认'
|
||||
text: i18n.t('CONFIRM')
|
||||
});
|
||||
confirmButton.addEventListener('click', () => {
|
||||
this.confirmed = true;
|
||||
|
|
|
|||
|
|
@ -1,83 +1,69 @@
|
|||
import { BaseModal } from "./BaseModal";
|
||||
import { setIcon } from "obsidian";
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class ContactModal extends BaseModal {
|
||||
constructor(container: HTMLElement) {
|
||||
super(container, '关于作者');
|
||||
super(container, i18n.t('ABOUT_AUTHOR'));
|
||||
}
|
||||
|
||||
protected createContent() {
|
||||
const content = this.element.createDiv({ cls: 'contact-content' });
|
||||
|
||||
// 作者介绍
|
||||
const intro = content.createDiv({ cls: 'author-intro' });
|
||||
intro.createEl('p', { text: i18n.t('AUTHOR_INTRO_1') });
|
||||
|
||||
intro.createEl('p', {
|
||||
text: '你好,我是【夜半】,一名全职写作与独立开发者。'
|
||||
});
|
||||
|
||||
// 使用HTML元素添加带有强调样式的文本
|
||||
const p1 = intro.createEl('p');
|
||||
|
||||
p1.createSpan({ text: '这款插件是' });
|
||||
p1.createSpan({ text: '我为了帮助在Obsidian中进行长篇创作的作者而开发的工具', cls: 'text-accent' });
|
||||
p1.createSpan({ text: ',希望能' });
|
||||
p1.createSpan({ text: '让你的写作过程更流畅,创作体验更愉悦', cls: 'text-accent' });
|
||||
p1.createSpan({ text: '。' });
|
||||
p1.createSpan({ text: i18n.t('AUTHOR_INTRO_2_1') });
|
||||
p1.createSpan({ text: i18n.t('AUTHOR_INTRO_2_2'), cls: 'text-accent' });
|
||||
p1.createSpan({ text: i18n.t('AUTHOR_INTRO_2_3') });
|
||||
p1.createSpan({ text: i18n.t('AUTHOR_INTRO_2_4'), cls: 'text-accent' });
|
||||
p1.createSpan({ text: i18n.t('AUTHOR_INTRO_2_5') });
|
||||
|
||||
const p2 = intro.createEl('p');
|
||||
p2.createSpan({ text: '如果这款插件对你的写作有所帮助,或者你愿意支持我的独立开发与创作,欢迎请我喝咖啡☕。', cls: 'text-accent' });
|
||||
p2.createSpan({ text: i18n.t('AUTHOR_INTRO_3'), cls: 'text-accent' });
|
||||
|
||||
const p3 = intro.createEl('p');
|
||||
p3.createSpan({ text: '你的支持意义重大', cls: 'text-accent' });
|
||||
p3.createSpan({ text: ',它能让我更专注地开发更多实用工具,助力你的创作之旅。' });
|
||||
p3.createSpan({ text: i18n.t('AUTHOR_INTRO_4_1'), cls: 'text-accent' });
|
||||
p3.createSpan({ text: i18n.t('AUTHOR_INTRO_4_2') });
|
||||
|
||||
|
||||
// 赞赏支持区域 - 改为卡片式设计
|
||||
const donateSection = content.createDiv({ cls: 'info-card donate-section' });
|
||||
donateSection.createSpan({ cls: 'donate-text', text: '如需支持作者:' });
|
||||
const donateBtn = donateSection.createDiv({ cls: 'donate-button', text: '赞赏支持' });
|
||||
donateSection.createSpan({ cls: 'donate-text', text: i18n.t('DONATE_TEXT') });
|
||||
const donateBtn = donateSection.createDiv({ cls: 'donate-button', text: i18n.t('DONATE_BUTTON') });
|
||||
donateBtn.addEventListener('click', () => {
|
||||
this.close();
|
||||
const event = new CustomEvent('open-donate-modal');
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
|
||||
// 了解更多区域 - 改为卡片式设计
|
||||
const moreInfo = content.createDiv({ cls: 'info-card more-info-section' });
|
||||
moreInfo.createEl('p', {
|
||||
text: '如果你想了解更多关于写作、创作技巧的内容,或者关注我未来的作品动态,欢迎关注我的社交媒体。'
|
||||
});
|
||||
moreInfo.createEl('p', { text: i18n.t('MORE_INFO_TEXT') });
|
||||
|
||||
// 联系方式标题
|
||||
content.createEl('h3', { text: '联系方式:', cls: 'contact-title' });
|
||||
content.createEl('h3', { text: i18n.t('CONTACT_TITLE'), cls: 'contact-title' });
|
||||
|
||||
// 联系方式卡片
|
||||
const contactMethods = content.createDiv({ cls: 'contact-methods' });
|
||||
|
||||
this.createContactCard(contactMethods, 'file-text', '公众号', '夜半');
|
||||
this.createContactCard(contactMethods, 'book-open', '小红书', '夜半Yeban');
|
||||
this.createContactCard(contactMethods, 'message-circle', '微信', 'Bruce169229(添加注明来意)');
|
||||
this.createContactCard(contactMethods, 'globe', 'GitHub', 'https://github.com/Yeban8090');
|
||||
this.createContactCard(contactMethods, 'file-text', i18n.t('CONTACT_WECHAT_OFFICIAL'), '夜半');
|
||||
this.createContactCard(contactMethods, 'book-open', i18n.t('CONTACT_XIAOHONGSHU'), '夜半Yeban');
|
||||
this.createContactCard(contactMethods, 'message-circle', i18n.t('CONTACT_WECHAT'), 'Bruce169229(添加注明来意)');
|
||||
this.createContactCard(contactMethods, 'globe', i18n.t('CONTACT_GITHUB'), 'https://github.com/Yeban8090');
|
||||
}
|
||||
|
||||
private createContactCard(container: HTMLElement, icon: string, title: string, value: string) {
|
||||
const card = container.createDiv({ cls: 'contact-card' });
|
||||
|
||||
// 图标容器
|
||||
const iconContainer = card.createDiv({ cls: 'contact-icon-container' });
|
||||
setIcon(iconContainer, icon);
|
||||
|
||||
// 内容区域
|
||||
const contentContainer = card.createDiv({ cls: 'contact-info' });
|
||||
contentContainer.createDiv({ cls: 'contact-label', text: title });
|
||||
contentContainer.createDiv({ cls: 'contact-value', text: value });
|
||||
|
||||
card.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(value).then(() => {
|
||||
this.showNotice(`${title}已复制到剪贴板`);
|
||||
this.showNotice(i18n.t('COPY_SUCCESS', { type: title }));
|
||||
}).catch(() => {
|
||||
this.showNotice(`复制失败,请手动复制:${value}`);
|
||||
this.showNotice(i18n.t('COPY_FAILED', { value }));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { App, Modal, Setting, Notice } from 'obsidian';
|
|||
import { BookBasicInfo, Book } from '../types/book';
|
||||
import BookSmithPlugin from '../main';
|
||||
import { TemplateManager } from '../services/TemplateManager';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class CreateBookModal extends Modal {
|
||||
private bookInfo: Partial<BookBasicInfo> = {
|
||||
|
|
@ -23,12 +24,12 @@ export class CreateBookModal extends Modal {
|
|||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.addClass('book-smith-create-book-modal');
|
||||
contentEl.createEl('h2', { text: '创建新书籍' });
|
||||
contentEl.createEl('h2', { text: i18n.t('CREATE_BOOK_TITLE') });
|
||||
|
||||
// 添加模板选择
|
||||
new Setting(contentEl)
|
||||
.setName('模板')
|
||||
.setDesc('请选择书籍模板')
|
||||
.setName(i18n.t('BOOK_TEMPLATE'))
|
||||
.setDesc(i18n.t('TEMPLATE_CHECK_DESC'))
|
||||
.addDropdown(dropdown => {
|
||||
const templates = this.templateManager.getAllTemplateTypes();
|
||||
templates.forEach(template => {
|
||||
|
|
@ -43,10 +44,10 @@ export class CreateBookModal extends Modal {
|
|||
});
|
||||
// 添加封面上传
|
||||
new Setting(contentEl)
|
||||
.setName('封面')
|
||||
.setDesc('选择封面图片(可选)')
|
||||
.setName(i18n.t('COVER'))
|
||||
.setDesc(i18n.t('COVER_DESC'))
|
||||
.addButton(button => button
|
||||
.setButtonText('选择图片')
|
||||
.setButtonText(i18n.t('SELECT_IMAGE'))
|
||||
.onClick(async () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
|
|
@ -66,33 +67,34 @@ export class CreateBookModal extends Modal {
|
|||
const coverPath = `${coversPath}/${fileName}`;
|
||||
await this.plugin.app.vault.adapter.writeBinary(coverPath, await file.arrayBuffer());
|
||||
this.bookInfo.cover = coverPath;
|
||||
new Notice('封面上传成功');
|
||||
new Notice(i18n.t('COVER_UPLOAD_SUCCESS'));
|
||||
} catch (error) {
|
||||
new Notice('封面上传失败:' + error.message);
|
||||
new Notice(i18n.t('COVER_UPLOAD_FAILED') + error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('书名')
|
||||
.setDesc('请输入书籍标题')
|
||||
.setName(i18n.t('BOOK_TITLE'))
|
||||
.setDesc(i18n.t('BOOK_TITLE_DESC'))
|
||||
.addText(text => text
|
||||
.setPlaceholder('书名')
|
||||
.setPlaceholder(i18n.t('BOOK_TITLE_PLACEHOLDER'))
|
||||
.onChange(value => this.bookInfo.title = value));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('副标题')
|
||||
.setDesc('可选')
|
||||
.setName(i18n.t('SUBTITLE'))
|
||||
.setDesc(i18n.t('SUBTITLE_DESC'))
|
||||
.addText(text => text
|
||||
.setPlaceholder('副标题')
|
||||
.setPlaceholder(i18n.t('SUBTITLE_PLACEHOLDER'))
|
||||
.onChange(value => this.bookInfo.subtitle = value));
|
||||
// 添加目标字数设置
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('目标字数')
|
||||
.setDesc('设置本书预计总字数:万字')
|
||||
.setName(i18n.t('TARGET_WORDS'))
|
||||
.setDesc(i18n.t('TARGET_WORDS_DESC'))
|
||||
.addText(text => text
|
||||
.setPlaceholder('例如:20或20.0万')
|
||||
.setPlaceholder(i18n.t('TARGET_WORDS_PLACEHOLDER'))
|
||||
.onChange(value => {
|
||||
// 处理输入的字数
|
||||
const match = value.match(/^(\d+(?:\.\d+)?)万?$/);
|
||||
|
|
@ -103,35 +105,29 @@ export class CreateBookModal extends Modal {
|
|||
this.targetTotalWords = 10000;
|
||||
}
|
||||
}));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('作者')
|
||||
.setDesc('请输入作者名称,多个作者用逗号分隔')
|
||||
.setName(i18n.t('AUTHOR'))
|
||||
.setDesc(i18n.t('AUTHOR_DESC'))
|
||||
.addText(text => text
|
||||
.setPlaceholder('作者')
|
||||
.setPlaceholder(i18n.t('AUTHOR_PLACEHOLDER'))
|
||||
.setValue(this.plugin.settings.defaultAuthor || '')
|
||||
.onChange(value => this.bookInfo.author = value ? value.split(',') : []));
|
||||
|
||||
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('简介')
|
||||
.setDesc('请输入书籍简介')
|
||||
.setName(i18n.t('DESCRIPTION'))
|
||||
.setDesc(i18n.t('DESCRIPTION_DESC'))
|
||||
.addTextArea(text => text
|
||||
.setPlaceholder('书籍简介')
|
||||
.setPlaceholder(i18n.t('DESCRIPTION_PLACEHOLDER'))
|
||||
.onChange(value => this.bookInfo.desc = value));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 修改创建按钮的处理
|
||||
new Setting(contentEl)
|
||||
.addButton(btn => btn
|
||||
.setButtonText('创建')
|
||||
.setButtonText(i18n.t('CREATE'))
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
if (!this.validateBookInfo()) {
|
||||
new Notice('请填写必要信息');
|
||||
new Notice(i18n.t('REQUIRED_FIELDS'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
|
@ -140,13 +136,13 @@ export class CreateBookModal extends Modal {
|
|||
this.selectedTemplate,
|
||||
this.targetTotalWords
|
||||
);
|
||||
new Notice('书籍创建成功');
|
||||
new Notice(i18n.t('CREATE_SUCCESS'));
|
||||
if (this.onBookCreated) {
|
||||
this.onBookCreated(newBook);
|
||||
}
|
||||
this.close();
|
||||
} catch (error) {
|
||||
new Notice(`创建失败: ${error.message}`);
|
||||
new Notice(i18n.t('CREATE_FAILED') + error.message);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,18 @@ import { Notice } from "obsidian";
|
|||
import { BaseModal } from "./BaseModal";
|
||||
import { WechatQRCode } from '../assets/wechat-qrcode';
|
||||
import { AlipayQRCode } from '../assets/alipay-qrcode';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class DonateModal extends BaseModal {
|
||||
private selectedAmount: number = 6;
|
||||
private amounts = [
|
||||
{ value: 6, label: '暖心咖啡', icon: '☕️', feedback: '感谢您的咖啡赞助!' },
|
||||
{ value: 18, label: '章节赞助', icon: '📖', feedback: '给您一次新功能投票权' },
|
||||
{ value: 66, label: '功能共建', icon: '🎨', feedback: '邀请您加入内测社群,公众号链接我' }
|
||||
{ value: 6, label: i18n.t('DONATE_AMOUNT_COFFEE'), icon: '☕️', feedback: i18n.t('DONATE_FEEDBACK_COFFEE') },
|
||||
{ value: 18, label: i18n.t('DONATE_AMOUNT_CHAPTER'), icon: '📖', feedback: i18n.t('DONATE_FEEDBACK_CHAPTER') },
|
||||
{ value: 66, label: i18n.t('DONATE_AMOUNT_FEATURE'), icon: '🎨', feedback: i18n.t('DONATE_FEEDBACK_FEATURE') }
|
||||
];
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
super(container, '笔墨有情');
|
||||
super(container, i18n.t('DONATE_MODAL_TITLE'));
|
||||
}
|
||||
|
||||
protected createContent() {
|
||||
|
|
@ -23,38 +24,33 @@ export class DonateModal extends BaseModal {
|
|||
|
||||
private createCommunityStats() {
|
||||
const communityStats = this.element.createDiv({ cls: 'book-smith-community-stats' });
|
||||
|
||||
// 社区数据卡片
|
||||
const statsCard = communityStats.createDiv({ cls: 'stats-card' });
|
||||
|
||||
// 图标和标题
|
||||
const header = statsCard.createDiv({ cls: 'stats-header' });
|
||||
header.createSpan({ text: '📊', cls: 'stats-icon' });
|
||||
header.createSpan({ text: '社区数据', cls: 'stats-title' });
|
||||
header.createSpan({ text: i18n.t('COMMUNITY_STATS_TITLE'), cls: 'stats-title' });
|
||||
|
||||
// 数据列表
|
||||
const statsList = statsCard.createDiv({ cls: 'stats-list' });
|
||||
statsList.createEl('p', {
|
||||
text: '已有 1200+ 用户,32位支持者',
|
||||
text: i18n.t('COMMUNITY_STATS_USERS'),
|
||||
cls: 'stats-item'
|
||||
});
|
||||
statsList.createEl('p', {
|
||||
text: '平均每天创作 5000+ 字',
|
||||
text: i18n.t('COMMUNITY_STATS_WORDS'),
|
||||
cls: 'stats-item'
|
||||
});
|
||||
}
|
||||
|
||||
private createAmountPanel() {
|
||||
const panel = this.element.createDiv({ cls: 'book-smith-amount-panel' });
|
||||
|
||||
// 预设金额
|
||||
const presets = panel.createDiv({ cls: 'amount-presets' });
|
||||
|
||||
this.amounts.forEach(amount => {
|
||||
const btn = presets.createDiv({ cls: 'amount-btn' });
|
||||
const content = btn.createDiv({ cls: 'amount-content' });
|
||||
content.createSpan({ cls: 'amount-icon', text: amount.icon });
|
||||
content.createSpan({ cls: 'amount-label', text: amount.label });
|
||||
content.createSpan({ cls: 'amount-value', text: `${amount.value}元` });
|
||||
content.createSpan({ cls: 'amount-value', text: `${amount.value}${i18n.t('CURRENCY_UNIT')}` });
|
||||
|
||||
if (amount.value === this.selectedAmount) {
|
||||
btn.addClass('selected');
|
||||
|
|
@ -71,15 +67,18 @@ export class DonateModal extends BaseModal {
|
|||
private createPaymentChannels() {
|
||||
const channels = this.element.createDiv({ cls: 'payment-channels' });
|
||||
|
||||
// 支付方式选项卡
|
||||
const tabs = channels.createDiv({ cls: 'payment-tabs' });
|
||||
const wechatTab = tabs.createDiv({
|
||||
cls: 'payment-tab active',
|
||||
text: '微信赞赏'
|
||||
text: i18n.t('PAYMENT_WECHAT')
|
||||
});
|
||||
const alipayTab = tabs.createDiv({
|
||||
cls: 'payment-tab',
|
||||
text: '支付宝赞赏'
|
||||
text: i18n.t('PAYMENT_ALIPAY')
|
||||
});
|
||||
const kofiTab = tabs.createDiv({
|
||||
cls: 'payment-tab',
|
||||
text: i18n.t('PAYMENT_KOFI')
|
||||
});
|
||||
|
||||
// 二维码展示区
|
||||
|
|
@ -88,7 +87,7 @@ export class DonateModal extends BaseModal {
|
|||
wechatQR.createEl('img', {
|
||||
attr: {
|
||||
src: WechatQRCode,
|
||||
alt: '微信支付'
|
||||
alt: i18n.t('PAYMENT_WECHAT')
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -96,24 +95,43 @@ export class DonateModal extends BaseModal {
|
|||
alipayQR.createEl('img', {
|
||||
attr: {
|
||||
src: AlipayQRCode,
|
||||
alt: '支付宝'
|
||||
alt: i18n.t('PAYMENT_ALIPAY')
|
||||
}
|
||||
});
|
||||
|
||||
const kofiQR = qrcodeContainer.createDiv({ cls: 'qrcode-item' });
|
||||
const kofiLink = kofiQR.createEl('a', {
|
||||
cls: 'kofi-link',
|
||||
href: 'https://ko-fi.com/bruceyeban',
|
||||
attr: { target: '_blank' }
|
||||
});
|
||||
kofiLink.createEl('img', {
|
||||
attr: {
|
||||
src: 'https://storage.ko-fi.com/cdn/kofi3.png?v=3',
|
||||
alt: i18n.t('PAYMENT_KOFI'),
|
||||
style: 'height: 50px;'
|
||||
}
|
||||
});
|
||||
|
||||
// 切换逻辑
|
||||
wechatTab.addEventListener('click', () => {
|
||||
wechatTab.addClass('active');
|
||||
alipayTab.removeClass('active');
|
||||
wechatQR.addClass('active');
|
||||
alipayQR.removeClass('active');
|
||||
this.activateTab(wechatTab, wechatQR, [alipayTab, kofiTab], [alipayQR, kofiQR]);
|
||||
});
|
||||
|
||||
alipayTab.addEventListener('click', () => {
|
||||
alipayTab.addClass('active');
|
||||
wechatTab.removeClass('active');
|
||||
alipayQR.addClass('active');
|
||||
wechatQR.removeClass('active');
|
||||
this.activateTab(alipayTab, alipayQR, [wechatTab, kofiTab], [wechatQR, kofiQR]);
|
||||
});
|
||||
|
||||
kofiTab.addEventListener('click', () => {
|
||||
this.activateTab(kofiTab, kofiQR, [wechatTab, alipayTab], [wechatQR, alipayQR]);
|
||||
});
|
||||
}
|
||||
|
||||
private activateTab(activeTab: HTMLElement, activeContent: HTMLElement, inactiveTabs: HTMLElement[], inactiveContents: HTMLElement[]) {
|
||||
activeTab.addClass('active');
|
||||
activeContent.addClass('active');
|
||||
inactiveTabs.forEach(tab => tab.removeClass('active'));
|
||||
inactiveContents.forEach(content => content.removeClass('active'));
|
||||
}
|
||||
|
||||
private selectAmount(amount: number) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { App, Modal, Setting, Notice } from 'obsidian';
|
|||
import { Book, BookBasicInfo } from '../types/book';
|
||||
import { BookManager } from '../services/BookManager';
|
||||
import BookSmithPlugin from '../main';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class EditBookModal extends Modal {
|
||||
private bookInfo: Partial<BookBasicInfo>;
|
||||
private targetTotalWords: number;
|
||||
|
|
@ -21,13 +23,13 @@ export class EditBookModal extends Modal {
|
|||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.addClass('book-smith-edit-book-modal');
|
||||
contentEl.createEl('h2', { text: '编辑书籍' });
|
||||
contentEl.createEl('h2', { text: i18n.t('EDIT_BOOK_TITLE') });
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('封面')
|
||||
.setDesc('选择封面图片(可选)')
|
||||
.setName(i18n.t('COVER'))
|
||||
.setDesc(i18n.t('COVER_DESC'))
|
||||
.addButton(button => button
|
||||
.setButtonText(this.bookInfo.cover ? '更换封面' : '选择封面')
|
||||
.setButtonText(this.bookInfo.cover ? i18n.t('CHANGE_COVER') : i18n.t('SELECT_COVER'))
|
||||
.onClick(async () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
|
|
@ -36,49 +38,45 @@ export class EditBookModal extends Modal {
|
|||
const file = input.files?.[0];
|
||||
if (file) {
|
||||
try {
|
||||
// 确保 covers 目录存在
|
||||
const coversPath = `${this.plugin.settings.defaultBookPath}/covers`;
|
||||
if (!await this.app.vault.adapter.exists(coversPath)) {
|
||||
await this.app.vault.adapter.mkdir(coversPath);
|
||||
}
|
||||
|
||||
// 复制图片到插件目录
|
||||
const fileName = `cover-${Date.now()}.${file.name.split('.').pop()}`;
|
||||
const coverPath = `${coversPath}/${fileName}`;
|
||||
await this.app.vault.adapter.writeBinary(coverPath, await file.arrayBuffer());
|
||||
this.bookInfo.cover = coverPath;
|
||||
new Notice('封面更新成功');
|
||||
new Notice(i18n.t('COVER_UPDATE_SUCCESS'));
|
||||
} catch (error) {
|
||||
new Notice('封面更新失败:' + error.message);
|
||||
new Notice(i18n.t('COVER_UPDATE_FAILED') + error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}));
|
||||
|
||||
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('书名')
|
||||
.setDesc('请输入书籍标题')
|
||||
.setName(i18n.t('BOOK_TITLE'))
|
||||
.setDesc(i18n.t('BOOK_TITLE_DESC'))
|
||||
.addText(text => text
|
||||
.setValue(this.bookInfo.title || '')
|
||||
.onChange(value => this.bookInfo.title = value));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('副标题')
|
||||
.setDesc('可选')
|
||||
.setName(i18n.t('SUBTITLE'))
|
||||
.setDesc(i18n.t('SUBTITLE_DESC'))
|
||||
.addText(text => text
|
||||
.setValue(this.bookInfo.subtitle || '')
|
||||
.onChange(value => this.bookInfo.subtitle = value));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('目标字数')
|
||||
.setDesc('设置本书预计总字数:万字')
|
||||
.setName(i18n.t('TARGET_WORDS'))
|
||||
.setDesc(i18n.t('TARGET_WORDS_DESC'))
|
||||
.addText(text => text
|
||||
.setPlaceholder('例如:20或20.0万')
|
||||
.setPlaceholder(i18n.t('TARGET_WORDS_PLACEHOLDER'))
|
||||
.setValue(`${Math.floor(this.targetTotalWords / 10000)}万`)
|
||||
.onChange(value => {
|
||||
// 处理输入的字数
|
||||
const match = value.match(/^(\d+(?:\.\d+)?)万?$/);
|
||||
if (match) {
|
||||
const num = parseFloat(match[1]) * 10000;
|
||||
|
|
@ -87,28 +85,28 @@ export class EditBookModal extends Modal {
|
|||
this.targetTotalWords = 10000;
|
||||
}
|
||||
}));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('作者')
|
||||
.setDesc('请输入作者名称,多个作者用逗号分隔')
|
||||
.setName(i18n.t('AUTHOR'))
|
||||
.setDesc(i18n.t('AUTHOR_DESC'))
|
||||
.addText(text => text
|
||||
.setValue(this.bookInfo.author?.join(',') || '')
|
||||
.onChange(value => this.bookInfo.author = value ? value.split(',') : []));
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('简介')
|
||||
.setDesc('请输入书籍简介')
|
||||
.setName(i18n.t('DESCRIPTION'))
|
||||
.setDesc(i18n.t('DESCRIPTION_DESC'))
|
||||
.addTextArea(text => text
|
||||
.setValue(this.bookInfo.desc || '')
|
||||
.onChange(value => this.bookInfo.desc = value));
|
||||
|
||||
// 修改保存按钮的处理
|
||||
new Setting(contentEl)
|
||||
.addButton(btn => btn
|
||||
.setButtonText('保存')
|
||||
.setButtonText(i18n.t('SAVE'))
|
||||
.setCta()
|
||||
.onClick(async () => {
|
||||
if (!this.validateBookInfo()) {
|
||||
new Notice('请填写必要信息');
|
||||
new Notice(i18n.t('REQUIRED_FIELDS'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
|
@ -119,11 +117,11 @@ export class EditBookModal extends Modal {
|
|||
target_total_words: this.targetTotalWords
|
||||
}
|
||||
});
|
||||
new Notice('保存成功');
|
||||
new Notice(i18n.t('SAVE_SUCCESS'));
|
||||
this.close();
|
||||
this.onSaved?.({ type: 'edited', bookId: this.book.basic.uuid });
|
||||
} catch (error) {
|
||||
new Notice(`保存失败: ${error.message}`);
|
||||
new Notice(i18n.t('SAVE_FAILED') + error.message);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { ConfirmModal } from './ConfirmModal';
|
|||
import { UnimportedBooksModal } from './UnimportedBooksModal'; // 添加导入
|
||||
import BookSmithPlugin from '../main';
|
||||
import { Book } from '../types/book';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class ManageBooksModal extends Modal {
|
||||
constructor(
|
||||
|
|
@ -22,7 +23,7 @@ export class ManageBooksModal extends Modal {
|
|||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
contentEl.addClass('book-smith-manage-books-modal');
|
||||
contentEl.createEl('h2', { text: '管理书籍' });
|
||||
contentEl.createEl('h2', { text: i18n.t('MANAGE_BOOKS_TITLE') });
|
||||
|
||||
// 添加搜索框和导入按钮的容器
|
||||
const topContainer = contentEl.createDiv({ cls: 'book-smith-manage-top-container' });
|
||||
|
|
@ -31,17 +32,13 @@ export class ManageBooksModal extends Modal {
|
|||
const searchContainer = topContainer.createDiv({ cls: 'book-smith-manage-search-container' });
|
||||
this.searchInput = searchContainer.createEl('input', {
|
||||
type: 'text',
|
||||
placeholder: '搜索书籍...',
|
||||
placeholder: i18n.t('SEARCH_BOOKS_PLACEHOLDER'),
|
||||
cls: 'book-smith-manage-search-input'
|
||||
});
|
||||
|
||||
this.searchInput.addEventListener('input', () => {
|
||||
this.renderBooks(this.filterBooks());
|
||||
});
|
||||
|
||||
// 添加导入按钮
|
||||
const importButton = topContainer.createEl('button', {
|
||||
text: '导入书籍',
|
||||
text: i18n.t('IMPORT_BOOK'),
|
||||
cls: 'book-smith-import-button'
|
||||
});
|
||||
|
||||
|
|
@ -60,18 +57,6 @@ export class ManageBooksModal extends Modal {
|
|||
this.renderBooks(this.books);
|
||||
}
|
||||
|
||||
private filterBooks(): Book[] {
|
||||
const searchTerm = this.searchInput.value.toLowerCase();
|
||||
if (!searchTerm) return this.books;
|
||||
|
||||
return this.books.filter(book =>
|
||||
book.basic.title.toLowerCase().includes(searchTerm) ||
|
||||
(book.basic.subtitle?.toLowerCase().includes(searchTerm)) ||
|
||||
book.basic.author.some(author => author.toLowerCase().includes(searchTerm)) ||
|
||||
(book.basic.desc?.toLowerCase().includes(searchTerm))
|
||||
);
|
||||
}
|
||||
|
||||
private renderBooks(books: Book[]) {
|
||||
this.bookList.empty();
|
||||
|
||||
|
|
@ -81,7 +66,7 @@ export class ManageBooksModal extends Modal {
|
|||
// 添加封面
|
||||
const coverContainer = bookContainer.createDiv({ cls: 'book-smith-book-cover' });
|
||||
if (book.basic.cover) {
|
||||
const coverImg = coverContainer.createEl('img', {
|
||||
coverContainer.createEl('img', {
|
||||
attr: {
|
||||
src: this.app.vault.adapter.getResourcePath(book.basic.cover),
|
||||
alt: book.basic.title
|
||||
|
|
@ -100,35 +85,35 @@ export class ManageBooksModal extends Modal {
|
|||
}
|
||||
}))
|
||||
.setDesc(
|
||||
`作者:${book.basic.author.join('、')}
|
||||
${book.basic.desc ? `\n简介:${book.basic.desc}` : ''}
|
||||
\n创作轨迹:${book.stats.total_words}${book.stats.target_total_words
|
||||
`${i18n.t('BOOK_AUTHOR_PREFIX')}${book.basic.author.join('、')}
|
||||
${book.basic.desc ? `${i18n.t('BOOK_DESC_PREFIX')}${book.basic.desc}` : ''}
|
||||
${i18n.t('BOOK_PROGRESS_PREFIX')}${book.stats.total_words}${book.stats.target_total_words
|
||||
? ` / ${(book.stats.target_total_words / 10000).toFixed(1)}万`
|
||||
: ' / 0万'
|
||||
}`
|
||||
);
|
||||
|
||||
setting.addButton(btn => btn
|
||||
.setButtonText('删除')
|
||||
.setButtonText(i18n.t('DELETE_BOOK'))
|
||||
.setWarning()
|
||||
.onClick(() => {
|
||||
new ConfirmModal(
|
||||
this.app,
|
||||
"删除书籍",
|
||||
`确定要删除《${book.basic.title}》吗?\n此操作不可恢复。`,
|
||||
i18n.t('DELETE_BOOK_TITLE'),
|
||||
i18n.t('DELETE_BOOK_DESC', { title: book.basic.title }),
|
||||
async () => {
|
||||
try {
|
||||
await this.plugin.bookManager.deleteBook(book.basic.uuid);
|
||||
new Notice('删除成功');
|
||||
new Notice(i18n.t('DELETE_SUCCESS'));
|
||||
this.onBookChange?.({ type: 'deleted', bookId: book.basic.uuid });
|
||||
this.onOpen();
|
||||
} catch (error) {
|
||||
new Notice(`删除失败: ${error.message}`);
|
||||
new Notice(i18n.t('DELETE_FAILED') + error.message);
|
||||
}
|
||||
}
|
||||
).open();
|
||||
})).addButton(btn => btn
|
||||
.setButtonText('编辑')
|
||||
.setButtonText(i18n.t('EDIT_BOOK'))
|
||||
.onClick(() => {
|
||||
new EditBookModal(
|
||||
this.app,
|
||||
|
|
@ -152,7 +137,7 @@ export class ManageBooksModal extends Modal {
|
|||
const rootFolder = this.app.vault.getAbstractFileByPath(booksPath);
|
||||
|
||||
if (!(rootFolder instanceof TFolder)) {
|
||||
new Notice('书籍根目录不存在或无法访问');
|
||||
new Notice(i18n.t('BOOKS_ROOT_NOT_FOUND'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -171,7 +156,7 @@ export class ManageBooksModal extends Modal {
|
|||
}
|
||||
}
|
||||
if (unimportedBooks.length === 0) {
|
||||
new Notice('没有找到未导入的书籍目录');
|
||||
new Notice(i18n.t('NO_UNIMPORTED_BOOKS'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +173,7 @@ export class ManageBooksModal extends Modal {
|
|||
).open();
|
||||
|
||||
} catch (error) {
|
||||
new Notice(`检测未导入书籍失败: ${error.message}`);
|
||||
new Notice(i18n.t('DETECT_UNIMPORTED_FAILED') + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -209,10 +194,10 @@ export class ManageBooksModal extends Modal {
|
|||
bookId: newBook.basic.uuid
|
||||
});
|
||||
|
||||
new Notice(`成功导入书籍《${newBook.basic.title}》`);
|
||||
new Notice(i18n.t('IMPORT_SUCCESS', { title: newBook.basic.title }));
|
||||
|
||||
} catch (error) {
|
||||
new Notice(`创建书籍配置失败: ${error.message}`);
|
||||
new Notice(i18n.t('IMPORT_FAILED') + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { App, Modal, Setting } from 'obsidian';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class NamePromptModal extends Modal {
|
||||
private result: string;
|
||||
|
|
@ -21,7 +22,7 @@ export class NamePromptModal extends Modal {
|
|||
contentEl.createEl('h2', { text: this.placeholder });
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('名称')
|
||||
.setName(i18n.t('NAME_LABEL'))
|
||||
.addText((text) => {
|
||||
text.setValue(this.defaultValue || '')
|
||||
.onChange((value) => {
|
||||
|
|
@ -39,7 +40,7 @@ export class NamePromptModal extends Modal {
|
|||
new Setting(contentEl)
|
||||
.addButton((btn) =>
|
||||
btn
|
||||
.setButtonText('确定')
|
||||
.setButtonText(i18n.t('CONFIRM'))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.close();
|
||||
|
|
@ -47,7 +48,7 @@ export class NamePromptModal extends Modal {
|
|||
}))
|
||||
.addButton((btn) =>
|
||||
btn
|
||||
.setButtonText('取消')
|
||||
.setButtonText(i18n.t('CANCEL'))
|
||||
.onClick(() => {
|
||||
this.close();
|
||||
this.onSubmit(null);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { App, Modal, Setting } from 'obsidian';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class ReferenceModal extends Modal {
|
||||
private result: string;
|
||||
|
|
@ -16,11 +17,11 @@ export class ReferenceModal extends Modal {
|
|||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.createEl('h3', { text: '添加引用内容' });
|
||||
contentEl.createEl('h3', { text: i18n.t('REFERENCE_MODAL_TITLE') });
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('引用内容')
|
||||
.setDesc('请输入引用内容的详细信息')
|
||||
.setName(i18n.t('REFERENCE_CONTENT'))
|
||||
.setDesc(i18n.t('REFERENCE_CONTENT_DESC'))
|
||||
.addTextArea((text) => {
|
||||
text
|
||||
.setValue(this.result)
|
||||
|
|
@ -32,7 +33,7 @@ export class ReferenceModal extends Modal {
|
|||
new Setting(contentEl)
|
||||
.addButton((btn) =>
|
||||
btn
|
||||
.setButtonText('确定')
|
||||
.setButtonText(i18n.t('CONFIRM'))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.onSubmit(this.result);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { App, Modal, Setting } from 'obsidian';
|
||||
import { Book } from '../types/book';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class SwitchBookModal extends Modal {
|
||||
constructor(
|
||||
|
|
@ -16,13 +17,13 @@ export class SwitchBookModal extends Modal {
|
|||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.addClass('book-smith-switch-book-modal');
|
||||
contentEl.createEl('h2', { text: '切换书籍' });
|
||||
contentEl.createEl('h2', { text: i18n.t('SWITCH_BOOK_TITLE') });
|
||||
|
||||
// 添加搜索框
|
||||
const searchContainer = contentEl.createDiv({ cls: 'book-smith-search-container' });
|
||||
this.searchInput = searchContainer.createEl('input', {
|
||||
type: 'text',
|
||||
placeholder: '搜索书籍...',
|
||||
placeholder: i18n.t('SEARCH_BOOK_PLACEHOLDER'),
|
||||
cls: 'book-smith-search-input'
|
||||
});
|
||||
|
||||
|
|
@ -57,12 +58,12 @@ export class SwitchBookModal extends Modal {
|
|||
new Setting(this.bookList)
|
||||
.setName(`《${book.basic.title}》${book.basic.subtitle ? ` - ${book.basic.subtitle}` : ''}`)
|
||||
.setDesc(
|
||||
`作者:${book.basic.author.join('、')}
|
||||
\n | 进度:${Math.round(book.stats.progress_by_chapter * 100)}% | 字数:${(book.stats.target_total_words / 10000).toFixed(1)}万
|
||||
\n | 最后修改:${new Date(book.stats.last_modified).toLocaleString()}`
|
||||
`${i18n.t('BOOK_AUTHOR_LABEL')}:${book.basic.author.join('、')}
|
||||
\n | ${i18n.t('BOOK_PROGRESS_LABEL')}:${Math.round(book.stats.progress_by_chapter * 100)}% | ${i18n.t('BOOK_WORDCOUNT_LABEL')}:${(book.stats.target_total_words / 10000).toFixed(1)}万
|
||||
\n | ${i18n.t('BOOK_LASTMOD_LABEL')}:${new Date(book.stats.last_modified).toLocaleString()}`
|
||||
)
|
||||
.addButton(btn => btn
|
||||
.setButtonText('选择')
|
||||
.setButtonText(i18n.t('SELECT_BOOK'))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.onSelect(book);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { App, Modal, Setting, setIcon, Menu, Notice } from 'obsidian';
|
|||
import { ChapterTree, ChapterNode } from '../types/book';
|
||||
import BookSmithPlugin from '../main';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class TemplateEditModal extends Modal {
|
||||
private templateName: string = '';
|
||||
|
|
@ -37,41 +38,41 @@ export class TemplateEditModal extends Modal {
|
|||
contentEl.empty();
|
||||
contentEl.addClass('book-smith-template-modal');
|
||||
|
||||
contentEl.createEl('h2', { text: this.isEdit ? '编辑模板' : '新建模板' });
|
||||
contentEl.createEl('h2', { text: this.isEdit ? i18n.t('TEMPLATE_EDIT_TITLE') : i18n.t('TEMPLATE_CREATE_TITLE') });
|
||||
|
||||
// 基本信息设置
|
||||
const formContainer = contentEl.createDiv('book-smith-template-form');
|
||||
new Setting(formContainer)
|
||||
.setName('模板名称')
|
||||
.setName(i18n.t('TEMPLATE_NAME'))
|
||||
.addText(text => text
|
||||
.setPlaceholder('请输入模板名称')
|
||||
.setPlaceholder(i18n.t('TEMPLATE_NAME_PLACEHOLDER'))
|
||||
.setValue(this.templateName)
|
||||
.onChange(value => this.templateName = value));
|
||||
|
||||
new Setting(formContainer)
|
||||
.setName('模板描述')
|
||||
.setName(i18n.t('TEMPLATE_DESC'))
|
||||
.addTextArea(text => text
|
||||
.setPlaceholder('可描述模板的简要结构')
|
||||
.setPlaceholder(i18n.t('TEMPLATE_DESC_PLACEHOLDER'))
|
||||
.setValue(this.templateDesc)
|
||||
.onChange(value => this.templateDesc = value));
|
||||
|
||||
// 模板结构编辑区域
|
||||
const structureContainer = contentEl.createDiv('book-smith-template-structure');
|
||||
structureContainer.createEl('h3', { text: '章节树' });
|
||||
structureContainer.createEl('h3', { text: i18n.t('TEMPLATE_STRUCTURE') });
|
||||
|
||||
// 添加新节点按钮
|
||||
new Setting(structureContainer)
|
||||
.addButton(btn => btn
|
||||
.setButtonText('添加文件')
|
||||
.setButtonText(i18n.t('ADD_FILE'))
|
||||
.onClick(() => {
|
||||
const newNode = this.createNewNode('file', '新章节');
|
||||
const newNode = this.createNewNode('file', i18n.t('NEW_CHAPTER'));
|
||||
this.templateStructure.tree.push(newNode);
|
||||
this.createNodeSetting(nodeListContainer, newNode, this.templateStructure.tree.length - 1);
|
||||
}))
|
||||
.addButton(btn => btn
|
||||
.setButtonText('添加文件夹')
|
||||
.setButtonText(i18n.t('ADD_FOLDER'))
|
||||
.onClick(() => {
|
||||
const newNode = this.createNewNode('group', '新目录');
|
||||
const newNode = this.createNewNode('group', i18n.t('NEW_DIRECTORY'));
|
||||
this.templateStructure.tree.push(newNode);
|
||||
this.createNodeSetting(nodeListContainer, newNode, this.templateStructure.tree.length - 1);
|
||||
}));
|
||||
|
|
@ -112,20 +113,20 @@ export class TemplateEditModal extends Modal {
|
|||
nodeContent.addEventListener('contextmenu', (e) => {
|
||||
const menu = new Menu();
|
||||
menu.addItem((item) => {
|
||||
item.setTitle('添加文件')
|
||||
item.setTitle(i18n.t('ADD_FILE'))
|
||||
.setIcon('file-plus')
|
||||
.onClick(() => {
|
||||
const newNode = this.createNewNode('file', '新章节', node);
|
||||
const newNode = this.createNewNode('file', i18n.t('NEW_CHAPTER'), node);
|
||||
node.children = node.children || [];
|
||||
node.children.push(newNode);
|
||||
this.refreshStructure(container);
|
||||
});
|
||||
});
|
||||
menu.addItem((item) => {
|
||||
item.setTitle('添加文件夹')
|
||||
item.setTitle(i18n.t('ADD_FOLDER'))
|
||||
.setIcon('folder-plus')
|
||||
.onClick(() => {
|
||||
const newNode = this.createNewNode('group', '新目录', node);
|
||||
const newNode = this.createNewNode('group', i18n.t('NEW_DIRECTORY'), node);
|
||||
node.children = node.children || [];
|
||||
node.children.push(newNode);
|
||||
this.refreshStructure(container);
|
||||
|
|
@ -275,27 +276,25 @@ export class TemplateEditModal extends Modal {
|
|||
}
|
||||
}
|
||||
|
||||
// 保存验证和提示
|
||||
async saveTemplate(): Promise<boolean> {
|
||||
// 验证模板名称
|
||||
if (!this.templateName.trim()) {
|
||||
new Notice('请输入模板名称');
|
||||
new Notice(i18n.t('TEMPLATE_NAME_REQUIRED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证是否有节点
|
||||
|
||||
if (this.templateStructure.tree.length === 0) {
|
||||
new Notice('请至少添加一个节点');
|
||||
new Notice(i18n.t('TEMPLATE_NODE_REQUIRED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const key = this.isEdit ?
|
||||
this.originalKey :
|
||||
`${uuidv4().slice(0, 8)}`;
|
||||
|
||||
// 如果是新建模板,检查是否已存在同名模板
|
||||
if (!this.isEdit && Object.values(this.plugin.settings.templates.custom).some(t => t.name === this.templateName)) {
|
||||
new Notice('已存在同名模板,请修改模板名称');
|
||||
new Notice(i18n.t('TEMPLATE_NAME_EXISTS'));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -305,15 +304,15 @@ export class TemplateEditModal extends Modal {
|
|||
structure: this.templateStructure,
|
||||
isBuiltin: false
|
||||
};
|
||||
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
new Notice('模板保存成功');
|
||||
new Notice(i18n.t('TEMPLATE_SAVE_SUCCESS'));
|
||||
|
||||
this.onSaved?.();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('保存模板失败:', error);
|
||||
new Notice('保存模板失败,请查看控制台了解详情');
|
||||
new Notice(i18n.t('TEMPLATE_SAVE_FAILED'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { App, Modal, setIcon } from 'obsidian';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class UnimportedBooksModal extends Modal {
|
||||
private result: string | null = null;
|
||||
|
|
@ -17,18 +18,17 @@ export class UnimportedBooksModal extends Modal {
|
|||
contentEl.empty();
|
||||
contentEl.addClass('book-smith-unimported-books-modal');
|
||||
|
||||
contentEl.createEl('h2', { text: '选择要导入的书籍目录' });
|
||||
contentEl.createEl('h2', { text: i18n.t('UNIMPORTED_BOOKS_TITLE') });
|
||||
|
||||
if (this.folders.length === 0) {
|
||||
contentEl.createEl('p', {
|
||||
cls: 'book-smith-unimported-empty-message',
|
||||
text: '没有找到未导入的书籍目录'
|
||||
text: i18n.t('NO_UNIMPORTED_FOLDERS')
|
||||
});
|
||||
|
||||
// 添加关闭按钮
|
||||
const buttonContainer = contentEl.createEl('div', { cls: 'book-smith-unimported-buttons' });
|
||||
const closeButton = buttonContainer.createEl('button', {
|
||||
text: '关闭',
|
||||
text: i18n.t('CLOSE'),
|
||||
cls: 'book-smith-unimported-button-cancel'
|
||||
});
|
||||
|
||||
|
|
@ -40,24 +40,22 @@ export class UnimportedBooksModal extends Modal {
|
|||
return;
|
||||
}
|
||||
|
||||
// 添加搜索框
|
||||
const searchContainer = contentEl.createEl('div', { cls: 'book-smith-unimported-search-container' });
|
||||
const searchInput = searchContainer.createEl('input', {
|
||||
type: 'text',
|
||||
placeholder: '搜索目录...',
|
||||
placeholder: i18n.t('SEARCH_FOLDERS_PLACEHOLDER'),
|
||||
cls: 'book-smith-unimported-search-input'
|
||||
});
|
||||
|
||||
const listEl = contentEl.createEl('div', { cls: 'book-smith-unimported-list' });
|
||||
|
||||
// 渲染文件夹列表
|
||||
const renderFolders = (folders: string[]) => {
|
||||
listEl.empty();
|
||||
|
||||
if (folders.length === 0) {
|
||||
listEl.createEl('div', {
|
||||
cls: 'book-smith-unimported-empty-result',
|
||||
text: '没有匹配的目录'
|
||||
text: i18n.t('NO_MATCHING_FOLDERS')
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -87,10 +85,8 @@ export class UnimportedBooksModal extends Modal {
|
|||
}
|
||||
};
|
||||
|
||||
// 初始渲染
|
||||
renderFolders(this.folders);
|
||||
|
||||
// 搜索功能
|
||||
searchInput.addEventListener('input', () => {
|
||||
const searchTerm = searchInput.value.toLowerCase();
|
||||
if (!searchTerm) {
|
||||
|
|
@ -108,12 +104,12 @@ export class UnimportedBooksModal extends Modal {
|
|||
const buttonContainer = contentEl.createEl('div', { cls: 'book-smith-unimported-buttons' });
|
||||
|
||||
const cancelButton = buttonContainer.createEl('button', {
|
||||
text: '取消',
|
||||
text: i18n.t('CANCEL'),
|
||||
cls: 'book-smith-unimported-button-cancel'
|
||||
});
|
||||
|
||||
const importButton = buttonContainer.createEl('button', {
|
||||
text: '导入',
|
||||
text: i18n.t('IMPORT'),
|
||||
cls: 'book-smith-unimported-button-import'
|
||||
});
|
||||
|
||||
|
|
@ -124,13 +120,11 @@ export class UnimportedBooksModal extends Modal {
|
|||
|
||||
importButton.addEventListener('click', () => {
|
||||
if (!this.result) {
|
||||
// 如果没有选择,显示提示
|
||||
const notice = contentEl.createEl('div', {
|
||||
cls: 'book-smith-unimported-notice',
|
||||
text: '请先选择一个目录'
|
||||
text: i18n.t('SELECT_FOLDER_FIRST')
|
||||
});
|
||||
|
||||
// 2秒后自动消失
|
||||
setTimeout(() => {
|
||||
notice.remove();
|
||||
}, 2000);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Book, BookBasicInfo, ChapterTree, ChapterNode } from '../types/book';
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { TemplateManager } from './TemplateManager';
|
||||
import { BookSmithSettings } from '../settings/settings';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class BookManager {
|
||||
private templateManager: TemplateManager;
|
||||
|
|
@ -72,12 +73,11 @@ export class BookManager {
|
|||
|
||||
// 确保目录结构存在
|
||||
private async ensureDirectoryStructure(basicInfo: Omit<BookBasicInfo, 'uuid' | 'created_at'>): Promise<void> {
|
||||
// 检查书籍是否已存在
|
||||
const existingFolder = this.app.vault.getAbstractFileByPath(
|
||||
`${this.rootPath}/${basicInfo.title}`
|
||||
);
|
||||
if (existingFolder) {
|
||||
throw new Error('书籍已存在');
|
||||
throw new Error(i18n.t('BOOK_EXISTS'));
|
||||
}
|
||||
|
||||
// 确保根目录存在
|
||||
|
|
@ -126,14 +126,14 @@ export class BookManager {
|
|||
async updateBook(uuid: string, updates: Partial<Book>): Promise<Book> {
|
||||
const book = await this.getBookById(uuid);
|
||||
if (!book) {
|
||||
throw new Error('书籍不存在');
|
||||
throw new Error(i18n.t('BOOK_NOT_FOUND'));
|
||||
}
|
||||
|
||||
const oldBookPath = `${this.settings.defaultBookPath}/${book.basic.title}`;
|
||||
const folder = this.app.vault.getAbstractFileByPath(oldBookPath);
|
||||
|
||||
if (!(folder instanceof TFolder)) {
|
||||
throw new Error('书籍文件夹不存在');
|
||||
throw new Error(i18n.t('BOOK_FOLDER_NOT_FOUND'));
|
||||
}
|
||||
|
||||
const updatedBook = {
|
||||
|
|
@ -160,9 +160,8 @@ export class BookManager {
|
|||
// 如果书名改变了,需要重命名文件夹
|
||||
if (updates.basic?.title && updates.basic.title !== book.basic.title) {
|
||||
const newBookPath = `${this.settings.defaultBookPath}/${updates.basic.title}`;
|
||||
// 检查新路径是否已存在
|
||||
if (this.app.vault.getAbstractFileByPath(newBookPath)) {
|
||||
throw new Error('书籍已存在');
|
||||
throw new Error(i18n.t('BOOK_EXISTS'));
|
||||
}
|
||||
await this.app.vault.rename(folder, newBookPath);
|
||||
}
|
||||
|
|
@ -174,7 +173,7 @@ export class BookManager {
|
|||
async deleteBook(uuid: string): Promise<void> {
|
||||
const book = await this.getBookById(uuid);
|
||||
if (!book) {
|
||||
throw new Error('书籍不存在');
|
||||
throw new Error(i18n.t('BOOK_NOT_FOUND'));
|
||||
}
|
||||
|
||||
const bookPath = `${this.settings.defaultBookPath}/${book.basic.title}`;
|
||||
|
|
@ -183,7 +182,7 @@ export class BookManager {
|
|||
if (folder) {
|
||||
await this.app.vault.trash(folder, true);
|
||||
} else {
|
||||
throw new Error('书籍文件夹不存在');
|
||||
throw new Error(i18n.t('BOOK_FOLDER_NOT_FOUND'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -217,7 +216,7 @@ export class BookManager {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error('保存配置文件时发生错误:', error);
|
||||
throw new Error('保存配置文件失败');
|
||||
throw new Error(i18n.t('SAVE_CONFIG_FAILED'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -265,7 +264,7 @@ export class BookManager {
|
|||
basic: {
|
||||
uuid: uuidv4(),
|
||||
title: folderName,
|
||||
author: this.settings.defaultAuthor ? [this.settings.defaultAuthor] : ['未知作者'],
|
||||
author: this.settings.defaultAuthor ? [this.settings.defaultAuthor] : [i18n.t('UNKNOWN_AUTHOR')],
|
||||
created_at: new Date().toISOString()
|
||||
},
|
||||
structure: {
|
||||
|
|
@ -292,14 +291,14 @@ export class BookManager {
|
|||
// 保存书籍配置
|
||||
const bookFolder = this.app.vault.getAbstractFileByPath(folderPath);
|
||||
if (!(bookFolder instanceof TFolder)) {
|
||||
throw new Error('书籍文件夹不存在');
|
||||
throw new Error(i18n.t('BOOK_FOLDER_NOT_FOUND'));
|
||||
}
|
||||
|
||||
await this.saveBookConfig(bookFolder, newBook);
|
||||
return newBook;
|
||||
} catch (error) {
|
||||
console.error('导入书籍失败:', error);
|
||||
throw error;
|
||||
throw new Error(i18n.t('IMPORT_BOOK_FAILED'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { MarkdownView, Notice } from 'obsidian';
|
||||
import BookSmithPlugin from '../main';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export interface FocusStats {
|
||||
interruptions: number; // 中断次数
|
||||
|
|
@ -235,10 +236,18 @@ export class FocusManager {
|
|||
this.state = FocusState.BREAK;
|
||||
this.remainingTime = this.plugin.settings.focus.breakDuration * 60;
|
||||
this.startTimer();
|
||||
new Notice('休息时间开始');
|
||||
new Notice(i18n.t('BREAK_TIME_START'));
|
||||
this.notifyUpdate();
|
||||
}
|
||||
|
||||
private showSummary(): void {
|
||||
new Notice(i18n.t('FOCUS_SUMMARY', {
|
||||
duration: this.plugin.settings.focus.workDuration,
|
||||
interruptions: this.stats.interruptions,
|
||||
words: this.currentWords
|
||||
}));
|
||||
}
|
||||
|
||||
// =============== 事件监听方法 ===============
|
||||
onUpdate(callback: () => void): void {
|
||||
this.updateListeners.push(callback);
|
||||
|
|
@ -269,9 +278,4 @@ export class FocusManager {
|
|||
}, wait);
|
||||
};
|
||||
}
|
||||
|
||||
private showSummary(): void {
|
||||
const summary = `专注结束!\n完成时间:${this.plugin.settings.focus.workDuration}分钟\n中断次数:${this.stats.interruptions}次\n本次字数:${this.currentWords}`;
|
||||
new Notice(summary);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import BookSmithPlugin from '../main';
|
|||
import { Book, ChapterNode } from '../types/book';
|
||||
import { Reference, ReferenceData, ChapterReferences } from '../types/reference';
|
||||
import { ReferenceModal } from '../modals/ReferenceModal';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class ReferenceManager {
|
||||
// === 1. 初始化 ===
|
||||
|
|
@ -39,10 +40,10 @@ export class ReferenceManager {
|
|||
}
|
||||
|
||||
private checkReferenceFile(bookPath: string): boolean {
|
||||
const referencePath = `${bookPath}/引用书目.md`;
|
||||
const referencePath = `${bookPath}/${i18n.t('REFERENCE_FILE_NAME')}`;
|
||||
const file = this.app.vault.getAbstractFileByPath(referencePath);
|
||||
if (!file) {
|
||||
new Notice('请先在书籍目录下创建"引用书目.md"文件');
|
||||
new Notice(i18n.t('REFERENCE_FILE_NOT_FOUND'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -191,10 +192,10 @@ export class ReferenceManager {
|
|||
|
||||
if (!this.checkReferenceFile(bookPath)) return;
|
||||
|
||||
const referencePath = `${bookPath}/引用书目.md`;
|
||||
const referencePath = `${bookPath}/${i18n.t('REFERENCE_FILE_NAME')}`;
|
||||
const file = this.app.vault.getAbstractFileByPath(referencePath);
|
||||
if (!(file instanceof TFile)) {
|
||||
throw new Error('引用书目文件不存在或类型错误');
|
||||
throw new Error(i18n.t('REFERENCE_FILE_ERROR'));
|
||||
}
|
||||
|
||||
references.chapters.sort((a, b) => {
|
||||
|
|
@ -253,7 +254,7 @@ export class ReferenceManager {
|
|||
private addEditReferenceMenuItem(menu: Menu, editor: Editor, bookPath: string, refId: string) {
|
||||
menu.addItem((item) => {
|
||||
item
|
||||
.setTitle("编辑当前引用")
|
||||
.setTitle(i18n.t('EDIT_REFERENCE'))
|
||||
.setIcon("edit")
|
||||
.onClick(async () => {
|
||||
const references = await this.getReferenceData(bookPath);
|
||||
|
|
@ -265,17 +266,10 @@ export class ReferenceManager {
|
|||
});
|
||||
}
|
||||
|
||||
private addDeleteReferenceMenuItem(
|
||||
menu: Menu,
|
||||
editor: Editor,
|
||||
bookPath: string,
|
||||
refId: string,
|
||||
linkPart: string,
|
||||
orderPart: string
|
||||
) {
|
||||
private addDeleteReferenceMenuItem(menu: Menu, editor: Editor, bookPath: string, refId: string, linkPart: string, orderPart: string) {
|
||||
menu.addItem((item) => {
|
||||
item
|
||||
.setTitle("删除当前引用")
|
||||
.setTitle(i18n.t('DELETE_REFERENCE'))
|
||||
.setIcon("trash")
|
||||
.onClick(async () => {
|
||||
const references = await this.getReferenceData(bookPath);
|
||||
|
|
@ -306,7 +300,7 @@ export class ReferenceManager {
|
|||
private addNewReferenceMenuItem(menu: Menu, editor: Editor, file: TFile | null) {
|
||||
menu.addItem((item) => {
|
||||
item
|
||||
.setTitle("插入新引用")
|
||||
.setTitle(i18n.t('INSERT_REFERENCE'))
|
||||
.setIcon("quote-glyph")
|
||||
.onClick(async () => {
|
||||
await this.handleReferenceInsertion(editor, file);
|
||||
|
|
@ -318,7 +312,7 @@ export class ReferenceManager {
|
|||
private async handleReferenceInsertion(editor: Editor, file: TFile | null) {
|
||||
const selectedText = editor.getSelection().trim();
|
||||
if (!selectedText || selectedText.length === 0) {
|
||||
new Notice("请选择要引用的文本");
|
||||
new Notice(i18n.t('SELECT_TEXT_TO_REFERENCE'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -354,7 +348,7 @@ export class ReferenceManager {
|
|||
if (editor && selectedText) {
|
||||
const found = this.findReferenceById(references, ref.id);
|
||||
if (found) {
|
||||
const referenceLink = `[[${bookPath}/引用书目#^${ref.id}|${selectedText}]]${this.toSuperscript(found.order)}`;
|
||||
const referenceLink = `[[${bookPath}/${i18n.t('REFERENCE_FILE_NAME')}#^${ref.id}|${selectedText}]]${this.toSuperscript(found.order)}`;
|
||||
editor.replaceSelection(referenceLink);
|
||||
}
|
||||
}
|
||||
|
|
@ -373,7 +367,7 @@ export class ReferenceManager {
|
|||
|
||||
const currentNode = this.findCurrentNode(file);
|
||||
if (!currentNode) {
|
||||
new Notice("无法获取当前章节信息");
|
||||
new Notice(i18n.t('CHAPTER_INFO_ERROR'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -389,7 +383,7 @@ export class ReferenceManager {
|
|||
chapter.references.push(newRef);
|
||||
await this.updateReferenceFiles(bookPath, references);
|
||||
|
||||
const referenceLink = `[[${bookPath}/引用书目#^${newRef.id}|${selectedText}]]${this.toSuperscript(newRef.order)}`;
|
||||
const referenceLink = `[[${bookPath}/${i18n.t('REFERENCE_FILE_NAME')}#^${newRef.id}|${selectedText}]]${this.toSuperscript(newRef.order)}`;
|
||||
editor.replaceSelection(referenceLink);
|
||||
}).open();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { ChapterTree } from '../types/book';
|
||||
import { BookSmithSettings } from '../settings/settings';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
export class TemplateManager {
|
||||
constructor(private settings: BookSmithSettings) {}
|
||||
|
|
@ -7,7 +8,7 @@ export class TemplateManager {
|
|||
getTemplate(templateType: string): ChapterTree {
|
||||
const template = this.settings.templates.custom[templateType];
|
||||
if (!template) {
|
||||
throw new Error(`模板 "${templateType}" 不存在`);
|
||||
throw new Error(i18n.t('TEMPLATE_TYPE_NOT_FOUND', { type: templateType }));
|
||||
}
|
||||
return JSON.parse(JSON.stringify(template.structure));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { App, PluginSettingTab, Setting, setIcon, Notice } from 'obsidian';
|
||||
import BookSmithPlugin from '../main';
|
||||
import { i18n, Locale } from '../i18n/i18n';
|
||||
import { TemplateEditModal } from '../modals/TemplateEditModal';
|
||||
import { ConfirmModal } from '../modals/ConfirmModal';
|
||||
|
||||
|
|
@ -15,15 +16,15 @@ export class BookSmithSettingTab extends PluginSettingTab {
|
|||
private createSection(containerEl: HTMLElement, title: string, renderContent: (contentEl: HTMLElement) => void) {
|
||||
const section = containerEl.createDiv('settings-section');
|
||||
const header = section.createDiv('settings-section-header');
|
||||
|
||||
|
||||
const toggle = header.createSpan('settings-section-toggle');
|
||||
setIcon(toggle, 'chevron-right');
|
||||
|
||||
|
||||
header.createEl('h4', { text: title });
|
||||
|
||||
|
||||
const content = section.createDiv('settings-section-content');
|
||||
renderContent(content);
|
||||
|
||||
|
||||
header.addEventListener('click', () => {
|
||||
const isExpanded = !section.hasClass('is-expanded');
|
||||
section.toggleClass('is-expanded', isExpanded);
|
||||
|
|
@ -34,14 +35,14 @@ export class BookSmithSettingTab extends PluginSettingTab {
|
|||
this.expandedSections.delete(title);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 根据保存的状态或默认第一个展开
|
||||
if (this.expandedSections.has(title) || (!containerEl.querySelector('.settings-section'))) {
|
||||
section.addClass('is-expanded');
|
||||
setIcon(toggle, 'chevron-down');
|
||||
this.expandedSections.add(title);
|
||||
}
|
||||
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
|
|
@ -50,25 +51,65 @@ export class BookSmithSettingTab extends PluginSettingTab {
|
|||
containerEl.empty();
|
||||
containerEl.addClass('book-smith-settings');
|
||||
|
||||
containerEl.createEl('h2', { text: 'Book Smith设置' });
|
||||
|
||||
containerEl.createEl('h2', { text: i18n.t('SETTINGS_TITLE') });
|
||||
|
||||
// 基本设置
|
||||
this.createSection(containerEl, '基本设置', el => this.renderBasicSettings(el));
|
||||
|
||||
this.createSection(containerEl, i18n.t('BASIC_SETTINGS'), el => this.renderBasicSettings(el));
|
||||
|
||||
// 模板设置
|
||||
this.createSection(containerEl, '模板设置', el => this.renderTemplateSettings(el));
|
||||
|
||||
this.createSection(containerEl, i18n.t('TEMPLATE_SETTINGS'), el => this.renderTemplateSettings(el));
|
||||
|
||||
// 写作工具箱设置
|
||||
this.createSection(containerEl, '写作工具箱设置', el => this.renderWritingToolsSettings(el));
|
||||
this.createSection(containerEl, i18n.t('WRITING_TOOLS_SETTINGS'), el => this.renderWritingToolsSettings(el));
|
||||
}
|
||||
|
||||
// 添加新的模板设置渲染方法
|
||||
|
||||
private renderBasicSettings(containerEl: HTMLElement): void {
|
||||
// 添加语言选择
|
||||
new Setting(containerEl)
|
||||
.setName(i18n.t('LANGUAGE_SETTING'))
|
||||
.setDesc(i18n.t('LANGUAGE_DESC'))
|
||||
.addDropdown(dropdown => {
|
||||
dropdown
|
||||
.addOption('zh-CN', '简体中文')
|
||||
.addOption('en', 'English')
|
||||
.setValue(this.plugin.settings.language)
|
||||
.onChange(async (value: Locale) => {
|
||||
this.plugin.settings.language = value;
|
||||
i18n.setLocale(value);
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(i18n.t('DEFAULT_AUTHOR'))
|
||||
.setDesc(i18n.t('DEFAULT_AUTHOR_DESC'))
|
||||
.addText(text => text
|
||||
.setPlaceholder(i18n.t('DEFAULT_AUTHOR_PLACEHOLDER'))
|
||||
.setValue(this.plugin.settings.defaultAuthor)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.defaultAuthor = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(i18n.t('BOOK_STORAGE_PATH'))
|
||||
.setDesc(i18n.t('BOOK_STORAGE_DESC'))
|
||||
.addText(text => text
|
||||
.setPlaceholder('books')
|
||||
.setValue(this.plugin.settings.defaultBookPath)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.defaultBookPath = value;
|
||||
await this.plugin.saveSettings();
|
||||
new Notice(i18n.t('STORAGE_PATH_CHANGED'));
|
||||
}));
|
||||
}
|
||||
|
||||
private renderTemplateSettings(containerEl: HTMLElement): void {
|
||||
|
||||
// 默认模板选择
|
||||
new Setting(containerEl)
|
||||
.setName('默认模板')
|
||||
.setDesc('创建新书籍时使用的默认模板')
|
||||
.setName(i18n.t('DEFAULT_TEMPLATE'))
|
||||
.setDesc(i18n.t('DEFAULT_TEMPLATE_DESC'))
|
||||
.addDropdown(dropdown => {
|
||||
const templates = this.plugin.settings.templates.custom;
|
||||
Object.keys(templates).forEach(key => {
|
||||
|
|
@ -80,11 +121,11 @@ export class BookSmithSettingTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// 模板列表
|
||||
const templateList = containerEl.createDiv('template-list');
|
||||
templateList.createEl('h4', { text: '书籍模板' });
|
||||
|
||||
templateList.createEl('h4', { text: i18n.t('BOOK_TEMPLATES') });
|
||||
|
||||
// 先渲染默认模板(内置模板)
|
||||
const defaultTemplate = this.plugin.settings.templates.custom['default'];
|
||||
if (defaultTemplate) {
|
||||
|
|
@ -93,7 +134,7 @@ export class BookSmithSettingTab extends PluginSettingTab {
|
|||
.setName(defaultTemplate.name)
|
||||
.setDesc(defaultTemplate.description);
|
||||
}
|
||||
|
||||
|
||||
// 渲染其他自定义模板
|
||||
Object.entries(this.plugin.settings.templates.custom)
|
||||
.filter(([key]) => key !== 'default')
|
||||
|
|
@ -128,57 +169,30 @@ export class BookSmithSettingTab extends PluginSettingTab {
|
|||
).open();
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
// 添加新模板按钮
|
||||
new Setting(containerEl)
|
||||
.addButton(btn => btn
|
||||
.setButtonText('添加新模板')
|
||||
.setButtonText(i18n.t('ADD_NEW_TEMPLATE'))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
new TemplateEditModal(
|
||||
this.app,
|
||||
this.app,
|
||||
this.plugin,
|
||||
undefined,
|
||||
() => this.display() // 添加回调函数
|
||||
() => this.display()
|
||||
).open();
|
||||
}));
|
||||
}
|
||||
|
||||
private renderBasicSettings(containerEl: HTMLElement): void {
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('默认作者')
|
||||
.setDesc('创建新书籍时的默认作者名')
|
||||
.addText(text => text
|
||||
.setPlaceholder('输入默认作者名')
|
||||
.setValue(this.plugin.settings.defaultAuthor)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.defaultAuthor = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('书籍存储路径')
|
||||
.setDesc('新建书籍的默认存储路径')
|
||||
.addText(text => text
|
||||
.setPlaceholder('books')
|
||||
.setValue(this.plugin.settings.defaultBookPath)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.defaultBookPath = value;
|
||||
await this.plugin.saveSettings();
|
||||
new Notice('存储路径已更改,请重启 Obsidian 或重新加载以使更改生效');
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
private renderWritingToolsSettings(containerEl: HTMLElement): void {
|
||||
|
||||
// 专注模式设置
|
||||
const focusSection = containerEl.createDiv();
|
||||
focusSection.createEl('h4', { text: '专注模式设置' });
|
||||
|
||||
focusSection.createEl('h4', { text: i18n.t('FOCUS_MODE_SETTINGS') });
|
||||
|
||||
new Setting(focusSection)
|
||||
.setName('专注时长')
|
||||
.setDesc('每个专注周期的工作时长(分钟)')
|
||||
.setName(i18n.t('FOCUS_DURATION'))
|
||||
.setDesc(i18n.t('FOCUS_DURATION_DESC'))
|
||||
.addText(text => text
|
||||
.setPlaceholder('25')
|
||||
.setValue(this.plugin.settings.focus.workDuration.toString())
|
||||
|
|
@ -186,10 +200,10 @@ export class BookSmithSettingTab extends PluginSettingTab {
|
|||
this.plugin.settings.focus.workDuration = Number(value) || 25;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
|
||||
new Setting(focusSection)
|
||||
.setName('间隔时长')
|
||||
.setDesc('每个专注周期后的休息时长(分钟)')
|
||||
.setName(i18n.t('BREAK_DURATION'))
|
||||
.setDesc(i18n.t('BREAK_DURATION_DESC'))
|
||||
.addText(text => text
|
||||
.setPlaceholder('5')
|
||||
.setValue(this.plugin.settings.focus.breakDuration.toString())
|
||||
|
|
@ -197,10 +211,10 @@ export class BookSmithSettingTab extends PluginSettingTab {
|
|||
this.plugin.settings.focus.breakDuration = Number(value) || 5;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
|
||||
new Setting(focusSection)
|
||||
.setName('字数目标')
|
||||
.setDesc('每个专注周期的目标字数')
|
||||
.setName(i18n.t('WORD_GOAL'))
|
||||
.setDesc(i18n.t('WORD_GOAL_DESC'))
|
||||
.addText(text => text
|
||||
.setPlaceholder('500')
|
||||
.setValue(this.plugin.settings.focus.wordGoal.toString())
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { defaultTemplate } from '../templates/default';
|
||||
import { ChapterTree } from '../types/book';
|
||||
import { Locale } from '../i18n/i18n';
|
||||
export interface BookSmithSettings {
|
||||
// 基础配置
|
||||
language: Locale;
|
||||
defaultAuthor: string;
|
||||
defaultBookPath: string;
|
||||
lastBookId?: string;
|
||||
|
|
@ -44,6 +46,7 @@ export interface BookSmithSettings {
|
|||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: BookSmithSettings = {
|
||||
language: 'zh-CN', // 默认中文
|
||||
defaultAuthor: '夜半',
|
||||
defaultBookPath: 'books',
|
||||
lastBookId: '',
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@
|
|||
.payment-tabs {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
gap: 1px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,22 +55,12 @@ export const defaultTemplate: ChapterTree = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'reference',
|
||||
title: '引用书目',
|
||||
type: 'file',
|
||||
path: '引用书目.md',
|
||||
order: 3,
|
||||
default_status: 'draft',
|
||||
created_at: new Date().toISOString(),
|
||||
last_modified: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: 'afterword',
|
||||
title: '后记',
|
||||
type: 'file',
|
||||
path: '后记.md',
|
||||
order: 4,
|
||||
order: 3,
|
||||
default_status: 'draft',
|
||||
created_at: new Date().toISOString(),
|
||||
last_modified: new Date().toISOString()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { SwitchBookModal } from '../modals/SwitchBookModal';
|
|||
import { ChapterTree } from '../components/ChapterTree';
|
||||
import { FileEventManager } from '../services/FileEventManager';
|
||||
import { ReferenceManager } from '../services/ReferenceManager';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
// === 视图类定义 ===
|
||||
export class BookSmithView extends ItemView {
|
||||
|
|
@ -103,7 +104,7 @@ export class BookSmithView extends ItemView {
|
|||
}
|
||||
|
||||
getDisplayText() {
|
||||
return '书籍管理';
|
||||
return i18n.t('BOOK_MANAGER');
|
||||
}
|
||||
|
||||
getIcon() {
|
||||
|
|
@ -126,28 +127,28 @@ export class BookSmithView extends ItemView {
|
|||
|
||||
const newBookBtn = toolbar.createEl('button', { cls: 'book-smith-toolbar-btn' });
|
||||
setIcon(newBookBtn, 'create-new');
|
||||
newBookBtn.appendChild(createSpan({ text: ' 新建' }));
|
||||
newBookBtn.appendChild(createSpan({ text: ` ${i18n.t('NEW_BOOK')}` }));
|
||||
newBookBtn.addEventListener('click', () => {
|
||||
new CreateBookModal(this.app, this.plugin, async (newBook) => {
|
||||
if (newBook) {
|
||||
this.plugin.settings.lastBookId = newBook.basic.uuid;
|
||||
await this.plugin.saveSettings();
|
||||
await this.refreshView();
|
||||
new Notice(`已切换到《${newBook.basic.title}》`);
|
||||
new Notice(i18n.t('SWITCHED_TO_BOOK', { title: newBook.basic.title }));
|
||||
}
|
||||
}).open();
|
||||
});
|
||||
|
||||
const switchBookBtn = toolbar.createEl('button', { cls: 'book-smith-toolbar-btn' });
|
||||
setIcon(switchBookBtn, 'switch');
|
||||
switchBookBtn.appendChild(createSpan({ text: ' 切换' }));
|
||||
switchBookBtn.appendChild(createSpan({ text: ` ${i18n.t('SWITCH_BOOK')}` }));
|
||||
switchBookBtn.addEventListener('click', () => {
|
||||
this.switchBook();
|
||||
});
|
||||
|
||||
const manageBookBtn = toolbar.createEl('button', { cls: 'book-smith-toolbar-btn' });
|
||||
setIcon(manageBookBtn, 'library');
|
||||
manageBookBtn.appendChild(createSpan({ text: ' 管理' }));
|
||||
manageBookBtn.appendChild(createSpan({ text: ` ${i18n.t('MANAGE_BOOK')}` }));
|
||||
manageBookBtn.addEventListener('click', async () => {
|
||||
new ManageBooksModal(this.app, this.plugin, async (result) => {
|
||||
if (result.type === 'imported' && result.bookId) {
|
||||
|
|
@ -155,14 +156,14 @@ export class BookSmithView extends ItemView {
|
|||
this.plugin.settings.lastBookId = result.bookId;
|
||||
await this.plugin.saveSettings();
|
||||
await this.refreshView();
|
||||
new Notice(`已导入并切换到新书籍`);
|
||||
new Notice(i18n.t('IMPORTED_AND_SWITCHED'));
|
||||
} else if (result.bookId === this.currentBook?.basic.uuid) {
|
||||
if (result.type === 'deleted') {
|
||||
this.plugin.settings.lastBookId = undefined;
|
||||
await this.plugin.saveSettings();
|
||||
this.currentBook = null;
|
||||
await this.refreshView();
|
||||
new Notice('当前书籍已被删除');
|
||||
new Notice(i18n.t('CURRENT_BOOK_DELETED'));
|
||||
} else if (result.type === 'edited') {
|
||||
await this.refreshView();
|
||||
}
|
||||
|
|
@ -177,38 +178,7 @@ export class BookSmithView extends ItemView {
|
|||
|
||||
helpBtnContainer.createEl('div', {
|
||||
cls: 'book-smith-help-tooltip',
|
||||
text: `👋 欢迎使用 BookSmith
|
||||
|
||||
开始使用
|
||||
• 打开右侧【写作工具箱】,激活创作辅助功能
|
||||
• 专注模式、创作灵感等工具一键可得
|
||||
|
||||
创作管理
|
||||
• 新建:选择模板创建书籍项目
|
||||
• 切换:在不同作品间自由切换
|
||||
• 管理:导入、编辑您的作品集
|
||||
• 模板:自定义专属写作框架
|
||||
|
||||
章节编排
|
||||
• 树形结构:直观展现层次结构
|
||||
• 拖拽排序:灵活调整章节顺序
|
||||
• 状态标记:追踪创作进度
|
||||
• 右键菜单:便捷的章节操作
|
||||
|
||||
创作助手
|
||||
• 实时统计:字数、进度实时更新
|
||||
• 数据分析:写作习惯深度统计
|
||||
• 专注模式:提升写作效率
|
||||
|
||||
小贴士
|
||||
• 支持自定义多种写作模板
|
||||
• 可通过拖拽快速调整章节
|
||||
• 右键点击可进行更多操作
|
||||
|
||||
✨ 愿 BookSmith 能让您享受创作的美好时光。
|
||||
|
||||
💝 赞赏支持
|
||||
如果 BookSmith 为您带来帮助,请前往右侧写作工具箱【赞赏捐赠】,支持我继续创作优雅工具。`
|
||||
text: i18n.t('HELP_TOOLTIP')
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -252,14 +222,14 @@ export class BookSmithView extends ItemView {
|
|||
// 渲染书籍信息
|
||||
const infoSection = bookContent.createDiv({ cls: 'book-smith-book-info' });
|
||||
const authorRow = infoSection.createDiv({ cls: 'book-smith-info-row' });
|
||||
authorRow.createSpan({ text: '作者', cls: 'book-smith-info-label' });
|
||||
authorRow.createSpan({ text: i18n.t('BOOK_AUTHOR'), cls: 'book-smith-info-label' });
|
||||
authorRow.createSpan({
|
||||
text: this.currentBook.basic.author.join(', '),
|
||||
cls: 'book-smith-info-value'
|
||||
});
|
||||
|
||||
|
||||
const descRow = infoSection.createDiv({ cls: 'book-smith-info-row' });
|
||||
descRow.createSpan({ text: '简介', cls: 'book-smith-info-label' });
|
||||
descRow.createSpan({ text: i18n.t('BOOK_DESCRIPTION'), cls: 'book-smith-info-label' });
|
||||
descRow.createSpan({
|
||||
text: this.currentBook.basic.desc || '',
|
||||
cls: 'book-smith-info-value description'
|
||||
|
|
@ -291,11 +261,11 @@ export class BookSmithView extends ItemView {
|
|||
private renderEmptyState(container: HTMLElement) {
|
||||
const emptyState = container.createDiv({ cls: 'book-smith-empty-state' });
|
||||
emptyState.createEl('p', {
|
||||
text: '👋 欢迎使用 BookSmith',
|
||||
text: i18n.t('WELCOME_MESSAGE'),
|
||||
cls: 'book-smith-empty-title'
|
||||
});
|
||||
emptyState.createEl('p', {
|
||||
text: '点击上方的"新建书籍"创建作品,或使用"切换"按钮选择已有书籍',
|
||||
text: i18n.t('EMPTY_STATE_HINT'),
|
||||
cls: 'book-smith-empty-desc'
|
||||
});
|
||||
}
|
||||
|
|
@ -309,21 +279,21 @@ export class BookSmithView extends ItemView {
|
|||
const todayWords = statsContainer.createDiv({ cls: 'book-smith-stat-item' });
|
||||
const todayWordsLabel = todayWords.createSpan();
|
||||
setIcon(todayWordsLabel, 'pencil');
|
||||
todayWordsLabel.appendChild(createSpan({ text: ' 今日字数' }));
|
||||
todayWordsLabel.appendChild(createSpan({ text: ` ${i18n.t('TODAY_WORDS')}` }));
|
||||
todayWords.createEl('span', {
|
||||
cls: 'book-smith-stat-value',
|
||||
text: `${this.currentBook.stats.daily_words[today] || 0}字`
|
||||
text: `${this.currentBook.stats.daily_words[today] || 0}${i18n.t('WORD_UNIT')}`
|
||||
});
|
||||
|
||||
// 总字数统计
|
||||
const wordCount = statsContainer.createDiv({ cls: 'book-smith-stat-item' });
|
||||
const wordCountLabel = wordCount.createSpan();
|
||||
setIcon(wordCountLabel, 'document');
|
||||
wordCountLabel.appendChild(createSpan({ text: ' 字数统计' }));
|
||||
wordCountLabel.appendChild(createSpan({ text: ` ${i18n.t('TOTAL_WORDS')}` }));
|
||||
wordCount.createEl('span', {
|
||||
cls: 'book-smith-stat-value',
|
||||
text: `${this.currentBook.stats.total_words}${this.currentBook.stats.target_total_words
|
||||
? ` / ${(this.currentBook.stats.target_total_words / 10000).toFixed(1)}万`
|
||||
? ` / ${(this.currentBook.stats.target_total_words / 10000).toFixed(1)}${i18n.t('TEN_THOUSAND')}`
|
||||
: ''
|
||||
}`
|
||||
});
|
||||
|
|
@ -332,7 +302,7 @@ export class BookSmithView extends ItemView {
|
|||
const progress = statsContainer.createDiv({ cls: 'book-smith-stat-item' });
|
||||
const progressLabel = progress.createSpan();
|
||||
setIcon(progressLabel, 'target');
|
||||
progressLabel.appendChild(createSpan({ text: ' 章节完成' }));
|
||||
progressLabel.appendChild(createSpan({ text: ` ${i18n.t('CHAPTER_COMPLETION')}` }));
|
||||
progress.createEl('span', {
|
||||
cls: 'book-smith-stat-value',
|
||||
text: `${Math.round(this.currentBook.stats.progress_by_chapter * 100)}%`
|
||||
|
|
@ -342,20 +312,20 @@ export class BookSmithView extends ItemView {
|
|||
const duration = statsContainer.createDiv({ cls: 'book-smith-stat-item' });
|
||||
const durationLabel = duration.createSpan();
|
||||
setIcon(durationLabel, 'clock');
|
||||
durationLabel.appendChild(createSpan({ text: ' 写作天数' }));
|
||||
durationLabel.appendChild(createSpan({ text: ` ${i18n.t('WRITING_DAYS')}` }));
|
||||
duration.createEl('span', {
|
||||
cls: 'book-smith-stat-value',
|
||||
text: `${this.currentBook.stats.writing_days}天`
|
||||
text: `${this.currentBook.stats.writing_days}${i18n.t('DAY_UNIT')}`
|
||||
});
|
||||
|
||||
// 日均字数
|
||||
const average = statsContainer.createDiv({ cls: 'book-smith-stat-item' });
|
||||
const averageLabel = average.createSpan();
|
||||
setIcon(averageLabel, 'calendar-clock');
|
||||
averageLabel.appendChild(createSpan({ text: ' 日均字数' }));
|
||||
averageLabel.appendChild(createSpan({ text: ` ${i18n.t('AVERAGE_DAILY_WORDS')}` }));
|
||||
average.createEl('span', {
|
||||
cls: 'book-smith-stat-value',
|
||||
text: `${Math.round(this.currentBook.stats.average_daily_words)}字`
|
||||
text: `${Math.round(this.currentBook.stats.average_daily_words)}${i18n.t('WORD_UNIT')}`
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -363,7 +333,7 @@ export class BookSmithView extends ItemView {
|
|||
private async switchBook() {
|
||||
const books = await this.plugin.bookManager.getAllBooks();
|
||||
if (books.length === 0) {
|
||||
new Notice('暂无可切换的书籍');
|
||||
new Notice(i18n.t('NO_BOOKS_TO_SWITCH'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -371,7 +341,7 @@ export class BookSmithView extends ItemView {
|
|||
this.plugin.settings.lastBookId = selectedBook.basic.uuid;
|
||||
await this.plugin.saveSettings();
|
||||
await this.refreshView();
|
||||
new Notice(`已切换到《${selectedBook.basic.title}》`);
|
||||
new Notice(i18n.t('SWITCHED_TO_BOOK', { title: selectedBook.basic.title }));
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,8 @@ import { EbookModal } from '../modals/EbookModal';
|
|||
import { CommunityModal } from '../modals/CommunityModal';
|
||||
import { ContactModal } from '../modals/ContactModal';
|
||||
import BookSmithPlugin from '../main';
|
||||
import { i18n } from '../i18n/i18n';
|
||||
|
||||
interface ToolItem {
|
||||
icon: string;
|
||||
text: string;
|
||||
|
|
@ -32,7 +34,7 @@ export class ToolView extends ItemView {
|
|||
|
||||
// 视图基础配置
|
||||
getViewType() { return 'book-smith-tool'; }
|
||||
getDisplayText() { return '写作工具箱'; }
|
||||
getDisplayText() { return i18n.t('WRITING_TOOLBOX'); }
|
||||
getIcon() { return 'wrench'; }
|
||||
|
||||
// 生命周期方法
|
||||
|
|
@ -74,37 +76,37 @@ export class ToolView extends ItemView {
|
|||
// 保持原有的图标和标题
|
||||
const mainIconSpan = titleContainer.createSpan({ cls: 'book-smith-panel-icon' });
|
||||
setIcon(mainIconSpan, 'archive');
|
||||
titleContainer.createSpan({ text: '写作工具箱' });
|
||||
titleContainer.createSpan({ text: i18n.t('WRITING_TOOLBOX') });
|
||||
}
|
||||
|
||||
// 工具组创建
|
||||
private createToolGroups(container: HTMLElement) {
|
||||
// 写作助手工具组
|
||||
if (this.plugin.settings.tools.assistant) {
|
||||
this.createToolGroup(container, '写作助手', [
|
||||
this.createToolGroup(container, i18n.t('WRITING_ASSISTANT'), [
|
||||
{
|
||||
icon: 'target',
|
||||
text: '专注模式',
|
||||
text: i18n.t('FOCUS_MODE'),
|
||||
hasProgress: true,
|
||||
onClick: () => this.enterFocusMode()
|
||||
},
|
||||
{
|
||||
icon: 'brain',
|
||||
text: '创作灵感',
|
||||
text: i18n.t('CREATIVE_INSPIRATION'),
|
||||
onClick: () => new InspirationModal(this.containerEl).open()
|
||||
},
|
||||
{
|
||||
icon: 'file-text',
|
||||
text: '人物档案',
|
||||
text: i18n.t('CHARACTER_PROFILES'),
|
||||
onClick: () => {
|
||||
new Notice('人物档案功能即将上线');
|
||||
new Notice(i18n.t('FEATURE_COMING_SOON', { feature: i18n.t('CHARACTER_PROFILES') }));
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'map',
|
||||
text: '世界构建',
|
||||
text: i18n.t('WORLD_BUILDING'),
|
||||
onClick: () => {
|
||||
new Notice('世界构建功能即将上线');
|
||||
new Notice(i18n.t('FEATURE_COMING_SOON', { feature: i18n.t('WORLD_BUILDING') }));
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
|
@ -112,24 +114,24 @@ export class ToolView extends ItemView {
|
|||
|
||||
// 导出发布工具组
|
||||
if (this.plugin.settings.tools.export) {
|
||||
this.createToolGroup(container, '导出发布', [
|
||||
this.createToolGroup(container, i18n.t('EXPORT_PUBLISH'), [
|
||||
{
|
||||
icon: 'edit-3',
|
||||
text: '设计排版',
|
||||
text: i18n.t('DESIGN_TYPOGRAPHY'),
|
||||
onClick: () => {
|
||||
new Notice('书籍设计排版功能即将上线');
|
||||
new Notice(i18n.t('FEATURE_COMING_SOON', { feature: i18n.t('DESIGN_TYPOGRAPHY') }));
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'book',
|
||||
text: '生成电子书',
|
||||
text: i18n.t('GENERATE_EBOOK'),
|
||||
onClick: () => new EbookModal(this.containerEl).open()
|
||||
},
|
||||
{
|
||||
icon: 'clock',
|
||||
text: '更多功能...',
|
||||
text: i18n.t('MORE_FEATURES'),
|
||||
onClick: () => {
|
||||
new Notice('更多功能等你一起共创');
|
||||
new Notice(i18n.t('MORE_FEATURES_MESSAGE'));
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
|
@ -137,21 +139,21 @@ export class ToolView extends ItemView {
|
|||
|
||||
// 写作圈子工具组
|
||||
if (this.plugin.settings.tools.community) {
|
||||
this.createToolGroup(container, '写作圈子', [
|
||||
this.createToolGroup(container, i18n.t('WRITING_COMMUNITY'), [
|
||||
{
|
||||
icon: 'users',
|
||||
text: '创作社区',
|
||||
text: i18n.t('CREATIVE_COMMUNITY'),
|
||||
extra: '',
|
||||
onClick: () => new CommunityModal(this.containerEl).open()
|
||||
},
|
||||
{
|
||||
icon: 'message-square',
|
||||
text: '联系作者',
|
||||
text: i18n.t('CONTACT_AUTHOR'),
|
||||
onClick: () => new ContactModal(this.containerEl).open()
|
||||
},
|
||||
{
|
||||
icon: 'heart',
|
||||
text: '赞助捐赠',
|
||||
text: i18n.t('DONATE_SUPPORT'),
|
||||
onClick: () => new DonateModal(this.containerEl).open()
|
||||
}
|
||||
]);
|
||||
|
|
@ -192,15 +194,15 @@ export class ToolView extends ItemView {
|
|||
|
||||
// 设置面板创建
|
||||
private createSettings(container: HTMLElement) {
|
||||
const settingsItem = this.createToolItem(container, 'settings', '面板设置');
|
||||
const settingsItem = this.createToolItem(container, 'settings', i18n.t('PANEL_SETTINGS'));
|
||||
settingsItem.addClass('settings');
|
||||
|
||||
settingsItem.addEventListener('contextmenu', (event) => {
|
||||
const menu = new Menu();
|
||||
|
||||
// 添加工具显隐选项
|
||||
this.addToolVisibilityMenuItem(menu, 'assistant', 'target', '写作助手');
|
||||
this.addToolVisibilityMenuItem(menu, 'export', 'download', '导出发布');
|
||||
this.addToolVisibilityMenuItem(menu, 'assistant', 'target', i18n.t('WRITING_ASSISTANT'));
|
||||
this.addToolVisibilityMenuItem(menu, 'export', 'download', i18n.t('EXPORT_PUBLISH'));
|
||||
|
||||
menu.showAtMouseEvent(event);
|
||||
});
|
||||
|
|
@ -209,7 +211,7 @@ export class ToolView extends ItemView {
|
|||
// 添加工具显隐菜单项
|
||||
private addToolVisibilityMenuItem(menu: Menu, key: keyof typeof this.plugin.settings.tools, icon: string, text: string) {
|
||||
menu.addItem(item => item
|
||||
.setTitle(`${this.plugin.settings.tools[key] ? '隐藏' : '显示'}${text}`)
|
||||
.setTitle(`${this.plugin.settings.tools[key] ? i18n.t('HIDE') : i18n.t('SHOW')}${text}`)
|
||||
.setIcon(icon)
|
||||
.setChecked(this.plugin.settings.tools[key])
|
||||
.onClick(async () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue