diff --git a/src/components/ChapterTree.ts b/src/components/ChapterTree.ts index e11d82f..2afc9bb 100644 --- a/src/components/ChapterTree.ts +++ b/src/components/ChapterTree.ts @@ -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 diff --git a/src/components/FocusToolView.ts b/src/components/FocusToolView.ts index 09414a3..d230cab 100644 --- a/src/components/FocusToolView.ts +++ b/src/components/FocusToolView.ts @@ -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'); } } diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts new file mode 100644 index 0000000..afebdc6 --- /dev/null +++ b/src/i18n/i18n.ts @@ -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; + + constructor() { + // 初始化为系统语言或默认为中文 + this.locale = this.getSystemLocale(); + this.translations = {} as Record; + 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 { + 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'; \ No newline at end of file diff --git a/src/i18n/interfaces/common.ts b/src/i18n/interfaces/common.ts new file mode 100644 index 0000000..bbaa0a6 --- /dev/null +++ b/src/i18n/interfaces/common.ts @@ -0,0 +1,10 @@ +// 通用翻译接口 +export interface CommonTranslation { + // 通用 + PLUGIN_NAME: string; + SETTINGS: string; + SAVE: string; + CANCEL: string; + HIDE: string; + SHOW: string; +} \ No newline at end of file diff --git a/src/i18n/interfaces/components/components.ts b/src/i18n/interfaces/components/components.ts new file mode 100644 index 0000000..778cfec --- /dev/null +++ b/src/i18n/interfaces/components/components.ts @@ -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; +} \ No newline at end of file diff --git a/src/i18n/interfaces/index.ts b/src/i18n/interfaces/index.ts new file mode 100644 index 0000000..ecaf099 --- /dev/null +++ b/src/i18n/interfaces/index.ts @@ -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'; \ No newline at end of file diff --git a/src/i18n/interfaces/manager/managers.ts b/src/i18n/interfaces/manager/managers.ts new file mode 100644 index 0000000..183d8ac --- /dev/null +++ b/src/i18n/interfaces/manager/managers.ts @@ -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; +} \ No newline at end of file diff --git a/src/i18n/interfaces/modals/modals.ts b/src/i18n/interfaces/modals/modals.ts new file mode 100644 index 0000000..8d2ec0d --- /dev/null +++ b/src/i18n/interfaces/modals/modals.ts @@ -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; +} \ No newline at end of file diff --git a/src/i18n/interfaces/modals/toolbarModals.ts b/src/i18n/interfaces/modals/toolbarModals.ts new file mode 100644 index 0000000..729539c --- /dev/null +++ b/src/i18n/interfaces/modals/toolbarModals.ts @@ -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; +} \ No newline at end of file diff --git a/src/i18n/interfaces/settings/settings.ts b/src/i18n/interfaces/settings/settings.ts new file mode 100644 index 0000000..2f9b911 --- /dev/null +++ b/src/i18n/interfaces/settings/settings.ts @@ -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; +} \ No newline at end of file diff --git a/src/i18n/interfaces/views/booksmith-view.ts b/src/i18n/interfaces/views/booksmith-view.ts new file mode 100644 index 0000000..1620fe6 --- /dev/null +++ b/src/i18n/interfaces/views/booksmith-view.ts @@ -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; +} \ No newline at end of file diff --git a/src/i18n/interfaces/views/tool-view.ts b/src/i18n/interfaces/views/tool-view.ts new file mode 100644 index 0000000..af95d32 --- /dev/null +++ b/src/i18n/interfaces/views/tool-view.ts @@ -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; +} \ No newline at end of file diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts new file mode 100644 index 0000000..17fd946 --- /dev/null +++ b/src/i18n/locales/en.ts @@ -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; diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts new file mode 100644 index 0000000..2bef03a --- /dev/null +++ b/src/i18n/locales/zh-CN.ts @@ -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; + diff --git a/src/main.ts b/src/main.ts index 75c5f52..04e72e4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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); diff --git a/src/modals/CommunityModal.ts b/src/modals/CommunityModal.ts index cdba1d5..1f5da22 100644 --- a/src/modals/CommunityModal.ts +++ b/src/modals/CommunityModal.ts @@ -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' })); }); }); } diff --git a/src/modals/ConfirmModal.ts b/src/modals/ConfirmModal.ts index cf9b123..85ed811 100644 --- a/src/modals/ConfirmModal.ts +++ b/src/modals/ConfirmModal.ts @@ -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; diff --git a/src/modals/ContactModal.ts b/src/modals/ContactModal.ts index 55260ed..61662de 100644 --- a/src/modals/ContactModal.ts +++ b/src/modals/ContactModal.ts @@ -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 })); }); }); } diff --git a/src/modals/CreateBookModal.ts b/src/modals/CreateBookModal.ts index 2193dca..51ee35b 100644 --- a/src/modals/CreateBookModal.ts +++ b/src/modals/CreateBookModal.ts @@ -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 = { @@ -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); } })); } diff --git a/src/modals/DonateModal.ts b/src/modals/DonateModal.ts index 906332f..768e5d4 100644 --- a/src/modals/DonateModal.ts +++ b/src/modals/DonateModal.ts @@ -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) { diff --git a/src/modals/EditBookModal.ts b/src/modals/EditBookModal.ts index 5c6f4b4..5c9a6c4 100644 --- a/src/modals/EditBookModal.ts +++ b/src/modals/EditBookModal.ts @@ -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; 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); } })); } diff --git a/src/modals/ManageBooksModal.ts b/src/modals/ManageBooksModal.ts index ff03ad2..1fa4aeb 100644 --- a/src/modals/ManageBooksModal.ts +++ b/src/modals/ManageBooksModal.ts @@ -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); } } } diff --git a/src/modals/NamePromptModal.ts b/src/modals/NamePromptModal.ts index 9b50166..e176161 100644 --- a/src/modals/NamePromptModal.ts +++ b/src/modals/NamePromptModal.ts @@ -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); diff --git a/src/modals/ReferenceModal.ts b/src/modals/ReferenceModal.ts index 406beb8..4ace518 100644 --- a/src/modals/ReferenceModal.ts +++ b/src/modals/ReferenceModal.ts @@ -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); diff --git a/src/modals/SwitchBookModal.ts b/src/modals/SwitchBookModal.ts index df7f5bb..1d345c0 100644 --- a/src/modals/SwitchBookModal.ts +++ b/src/modals/SwitchBookModal.ts @@ -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); diff --git a/src/modals/TemplateEditModal.ts b/src/modals/TemplateEditModal.ts index 72eaf0e..d6e5fb8 100644 --- a/src/modals/TemplateEditModal.ts +++ b/src/modals/TemplateEditModal.ts @@ -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 { - // 验证模板名称 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; } } diff --git a/src/modals/UnimportedBooksModal.ts b/src/modals/UnimportedBooksModal.ts index 0c9a890..b54e589 100644 --- a/src/modals/UnimportedBooksModal.ts +++ b/src/modals/UnimportedBooksModal.ts @@ -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); diff --git a/src/services/BookManager.ts b/src/services/BookManager.ts index 56eba5a..04f8656 100644 --- a/src/services/BookManager.ts +++ b/src/services/BookManager.ts @@ -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): Promise { - // 检查书籍是否已存在 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): Promise { 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 { 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')); } } diff --git a/src/services/FocusManager.ts b/src/services/FocusManager.ts index 3a13b4e..ee28ff4 100644 --- a/src/services/FocusManager.ts +++ b/src/services/FocusManager.ts @@ -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); - } } \ No newline at end of file diff --git a/src/services/ReferenceManager.ts b/src/services/ReferenceManager.ts index 4951710..41a1028 100644 --- a/src/services/ReferenceManager.ts +++ b/src/services/ReferenceManager.ts @@ -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(); } diff --git a/src/services/TemplateManager.ts b/src/services/TemplateManager.ts index 56417ba..ad8cf24 100644 --- a/src/services/TemplateManager.ts +++ b/src/services/TemplateManager.ts @@ -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)); } diff --git a/src/settings/SettingTab.ts b/src/settings/SettingTab.ts index ef36755..1f3d255 100644 --- a/src/settings/SettingTab.ts +++ b/src/settings/SettingTab.ts @@ -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()) diff --git a/src/settings/settings.ts b/src/settings/settings.ts index f78de2d..eb48769 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -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: '', diff --git a/src/styles/modals/donate-modal.css b/src/styles/modals/donate-modal.css index 0d42cd9..f708082 100644 --- a/src/styles/modals/donate-modal.css +++ b/src/styles/modals/donate-modal.css @@ -110,7 +110,7 @@ .payment-tabs { display: flex; justify-content: center; - gap: 10px; + gap: 1px; margin-bottom: 15px; } diff --git a/src/templates/default.ts b/src/templates/default.ts index 48f739a..0f1b187 100644 --- a/src/templates/default.ts +++ b/src/templates/default.ts @@ -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() diff --git a/src/views/BookSmithView.ts b/src/views/BookSmithView.ts index 0f8a5a2..4f41e45 100644 --- a/src/views/BookSmithView.ts +++ b/src/views/BookSmithView.ts @@ -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(); } } \ No newline at end of file diff --git a/src/views/ToolsView.ts b/src/views/ToolsView.ts index eaab0eb..5d2acb8 100644 --- a/src/views/ToolsView.ts +++ b/src/views/ToolsView.ts @@ -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 () => {