diff --git a/README.md b/README.md index d33e8d6..3779228 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 一个轻量的跨平台 Markdown 编辑器,基于 **Qt 5 / C++17** 构建。 -当前版本 **0.5.1**。 +当前版本 **0.6.0**。 ## 界面 @@ -14,10 +14,6 @@ ![源代码模式](docs/screenshot-source.png) -导出 PDF 前先给一个预览窗口,确认无误再选路径保存: - -![导出 PDF](docs/screenshot-export.png) - ## 功能 ### 编辑 @@ -62,14 +58,22 @@ ### 导出 PDF -**文件 → 导出 PDF…**(`Ctrl+E`)会先弹出预览窗口,里面就是最终排版; -点「保存 PDF…」选个位置即可。A4 纸、16mm 页边距,预览和导出用的是同一份 -排版文档,所见即所得。 +**文件 → 导出 PDF…**(`Ctrl+E`)直接弹保存对话框,选个位置就导出,没有中间预览。 +A4 纸、16mm 页边距。 导出用的是 Qt 自带的 `QPrinter` + `QTextDocument`,不需要额外的 PDF 库。 支持标题、列表(含任务列表)、引用、代码块、行内代码、粗体 / 斜体 / 删除线、 链接;图片只输出 alt 文字,表格暂时按原文输出。 +### 语言 + +界面支持中文和英文,在 **视图 → 语言** 里切换,立即生效、不需要重启。 +选择会被记住。 + +- 界面文案的中文就是源码本身;英文由 `translations/voiletmark_en.ts` 提供 +- Qt 自带的对话框(文件选择、消息框按钮)也会跟着切换, + 中文用的是 Qt 官方的翻译文件 + ### 文件 - 新建、打开、保存、另存为、最近打开(最多 8 条) @@ -169,7 +173,7 @@ VoiletMark --screenshot a.png # 渲染窗口截图后退出(调试 / 视觉 | `Ctrl+N` / `Ctrl+O` / `Ctrl+S` | 新建 / 打开 / 保存 | | `Ctrl+Shift+S` | 另存为 | | `Ctrl+Shift+O` | 打开文件夹 | -| `Ctrl+E` | 导出 PDF(先预览) | +| `Ctrl+E` | 导出 PDF | | `Ctrl+B` | 显示 / 隐藏侧栏 | | `Ctrl+Shift+M` | 源代码模式 | | `Ctrl+1` ~ `Ctrl+6` | 设为 1–6 级标题(再按取消) | diff --git a/VoiletMark.pro b/VoiletMark.pro index 0740806..0681823 100644 --- a/VoiletMark.pro +++ b/VoiletMark.pro @@ -1,6 +1,6 @@ TEMPLATE = app TARGET = VoiletMark -VERSION = 0.5.1 +VERSION = 0.6.0 QT += core gui widgets printsupport @@ -23,7 +23,7 @@ SOURCES += \ src/MarkdownEditor.cpp \ src/MarkdownHighlighter.cpp \ src/MarkdownRenderer.cpp \ - src/ExportDialog.cpp \ + src/LanguageManager.cpp \ src/ThemeManager.cpp HEADERS += \ @@ -31,7 +31,7 @@ HEADERS += \ src/MarkdownEditor.h \ src/MarkdownHighlighter.h \ src/MarkdownRenderer.h \ - src/ExportDialog.h \ + src/LanguageManager.h \ src/ThemeManager.h # Linux 桌面集成:make install 之后能在应用菜单里找到 diff --git a/docs/screenshot-export.png b/docs/screenshot-export.png deleted file mode 100644 index 12331f5..0000000 Binary files a/docs/screenshot-export.png and /dev/null differ diff --git a/resources.qrc b/resources.qrc index 86cefbc..be00728 100644 --- a/resources.qrc +++ b/resources.qrc @@ -1,5 +1,7 @@ packaging/voiletmark.png + translations/voiletmark_en.qm + translations/qtbase_zh_CN.qm diff --git a/src/ExportDialog.cpp b/src/ExportDialog.cpp deleted file mode 100644 index 918c9d6..0000000 --- a/src/ExportDialog.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "ExportDialog.h" - -#include "MarkdownRenderer.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -ExportDialog::ExportDialog(const QString &markdown, const QString &documentName, - const QString &startDirectory, QWidget *parent) - : QDialog(parent) - , m_documentName(documentName) - , m_startDirectory(startDirectory) -{ - setWindowTitle(tr("导出 PDF — %1") - .arg(documentName.isEmpty() ? tr("未命名") : documentName)); - - // 别让窗口比屏幕还高,否则底部的按钮会顶到屏幕外面去。 - const QRect available = screen() ? screen()->availableGeometry() : QRect(0, 0, 1280, 900); - resize(qMin(900, available.width() - 80), qMin(860, available.height() - 100)); - - QVBoxLayout *layout = new QVBoxLayout(this); - layout->setContentsMargins(16, 16, 16, 12); - layout->setSpacing(10); - - m_document = new QTextDocument(this); - MarkdownRenderer::render(markdown, m_document); - // A4 去掉 16mm 页边距后的正文宽度(96dpi 下的像素): - // 预览和打印用同一个页宽,看到的就是导出的样子。 - m_document->setPageSize(QSizeF(672.76, 1001.57)); - - m_preview = new QTextBrowser(this); - m_preview->setFrameShape(QFrame::NoFrame); - m_preview->setOpenExternalLinks(true); - m_preview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_preview->setFixedWidth(700); - m_preview->setDocument(m_document); - m_preview->setStyleSheet(QStringLiteral( - "QTextBrowser { background: #ffffff; color: #1f2328;" - " border: 1px solid #d8dde3; border-radius: 4px; }")); - - QHBoxLayout *center = new QHBoxLayout; - center->addStretch(1); - center->addWidget(m_preview); - center->addStretch(1); - layout->addLayout(center, 1); - - QLabel *hint = new QLabel(tr("A4 纸、16mm 页边距,内容和预览一致。"), this); - hint->setForegroundRole(QPalette::PlaceholderText); - - QPushButton *saveButton = new QPushButton(tr("保存 PDF…"), this); - saveButton->setDefault(true); - QPushButton *closeButton = new QPushButton(tr("关闭"), this); - - QHBoxLayout *buttons = new QHBoxLayout; - buttons->addWidget(hint); - buttons->addStretch(1); - buttons->addWidget(closeButton); - buttons->addWidget(saveButton); - layout->addLayout(buttons); - - connect(closeButton, &QPushButton::clicked, this, &QDialog::reject); - connect(saveButton, &QPushButton::clicked, this, &ExportDialog::savePdf); -} - -void ExportDialog::savePdf() -{ - QString suggested = m_documentName.isEmpty() - ? tr("未命名") - : QFileInfo(m_documentName).completeBaseName(); - suggested += QStringLiteral(".pdf"); - - QString target = QFileDialog::getSaveFileName( - this, tr("保存 PDF"), QDir(m_startDirectory).filePath(suggested), - tr("PDF 文件 (*.pdf)")); - if (target.isEmpty()) - return; - - if (!target.endsWith(QStringLiteral(".pdf"), Qt::CaseInsensitive)) - target += QStringLiteral(".pdf"); - - QPrinter printer(QPrinter::HighResolution); - printer.setOutputFormat(QPrinter::PdfFormat); - printer.setOutputFileName(target); - printer.setPageSize(QPageSize(QPageSize::A4)); - printer.setPageMargins(QMarginsF(16, 16, 16, 16), QPageLayout::Millimeter); - printer.setDocName(QFileInfo(target).completeBaseName()); - - m_document->print(&printer); - - if (!QFileInfo::exists(target) || QFileInfo(target).size() <= 0) { - QMessageBox::warning(this, tr("导出失败"), - tr("没能写出「%1」,换个位置再试一次。") - .arg(QDir::toNativeSeparators(target))); - return; - } - - QMessageBox::information(this, tr("导出完成"), - tr("已保存到\n%1").arg(QDir::toNativeSeparators(target))); - accept(); -} diff --git a/src/ExportDialog.h b/src/ExportDialog.h deleted file mode 100644 index cc6e2d2..0000000 --- a/src/ExportDialog.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -class QTextBrowser; -class QTextDocument; - -/// “导出 PDF”窗口:上方是排版后的预览,下方保存为 PDF。 -class ExportDialog : public QDialog -{ - Q_OBJECT - -public: - ExportDialog(const QString &markdown, const QString &documentName, - const QString &startDirectory, QWidget *parent = nullptr); - -private: - void savePdf(); - - QTextBrowser *m_preview = nullptr; - QTextDocument *m_document = nullptr; - QString m_documentName; - QString m_startDirectory; -}; diff --git a/src/LanguageManager.cpp b/src/LanguageManager.cpp new file mode 100644 index 0000000..dadad9a --- /dev/null +++ b/src/LanguageManager.cpp @@ -0,0 +1,82 @@ +#include "LanguageManager.h" + +#include +#include +#include +#include + +LanguageManager *LanguageManager::instance() +{ + static LanguageManager *manager = new LanguageManager(); + return manager; +} + +LanguageManager::LanguageManager(QObject *parent) + : QObject(parent) +{ +} + +QStringList LanguageManager::availableLanguages() +{ + return {QStringLiteral("zh_CN"), QStringLiteral("en")}; +} + +QString LanguageManager::displayName(const QString &code) +{ + if (code == QLatin1String("en")) + return QStringLiteral("English"); + return QStringLiteral("中文"); +} + +void LanguageManager::initialize() +{ + QSettings settings; + QString code = settings.value(QStringLiteral("ui/language")).toString(); + if (!availableLanguages().contains(code)) { + code = QLocale::system().language() == QLocale::English + ? QStringLiteral("en") + : QStringLiteral("zh_CN"); + } + setLanguage(code); +} + +void LanguageManager::setLanguage(const QString &code) +{ + const QString normalized = + (code == QLatin1String("en")) ? QStringLiteral("en") : QStringLiteral("zh_CN"); + + if (m_appTranslator) { + QCoreApplication::removeTranslator(m_appTranslator); + delete m_appTranslator; + m_appTranslator = nullptr; + } + if (m_qtTranslator) { + QCoreApplication::removeTranslator(m_qtTranslator); + delete m_qtTranslator; + m_qtTranslator = nullptr; + } + + if (normalized == QLatin1String("en")) { + // 源码文案是中文,英文需要一份中文 → 英文的翻译。 + auto *translator = new QTranslator(this); + if (translator->load(QStringLiteral(":/translations/voiletmark_en.qm"))) { + QCoreApplication::installTranslator(translator); + m_appTranslator = translator; + } else { + delete translator; + } + } else { + // 中文界面只需要把 Qt 自带的文件对话框 / 消息框也切过来。 + auto *translator = new QTranslator(this); + if (translator->load(QStringLiteral(":/translations/qtbase_zh_CN.qm"))) { + QCoreApplication::installTranslator(translator); + m_qtTranslator = translator; + } else { + delete translator; + } + } + + m_language = normalized; + QSettings().setValue(QStringLiteral("ui/language"), m_language); + emit languageChanged(); +} diff --git a/src/LanguageManager.h b/src/LanguageManager.h new file mode 100644 index 0000000..5908c67 --- /dev/null +++ b/src/LanguageManager.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +class QTranslator; + +/// 界面语言管理:目前只支持中文(zh_CN)和英文(en)。 +/// +/// 源码里的界面文案是中文,所以中文不需要额外的翻译文件, +/// 只要把 Qt 自带对话框(文件选择、消息框按钮等)切到中文; +/// 英文则加载一份中文 → 英文的翻译文件。 +class LanguageManager : public QObject +{ + Q_OBJECT + +public: + static LanguageManager *instance(); + + /// 读取设置(首次按系统语言猜一个)并应用。 + void initialize(); + + void setLanguage(const QString &code); + QString language() const { return m_language; } + + static QStringList availableLanguages(); + static QString displayName(const QString &code); + +signals: + void languageChanged(); + +private: + explicit LanguageManager(QObject *parent = nullptr); + + QString m_language = QStringLiteral("zh_CN"); + QTranslator *m_appTranslator = nullptr; + QTranslator *m_qtTranslator = nullptr; +}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 53d82ce..d782180 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,7 +1,8 @@ #include "MainWindow.h" -#include "ExportDialog.h" +#include "LanguageManager.h" #include "MarkdownEditor.h" +#include "MarkdownRenderer.h" #include "ThemeManager.h" #include @@ -21,7 +22,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -83,6 +87,49 @@ int main() --- )VOILETMARK"; +const char kWelcomeTextEn[] = R"VOILETMARK( +# Welcome to VoiletMark + +A lightweight cross-platform Markdown editor built with **Qt 5** and C++. + +## Quick start + +- `Ctrl+O` opens a file, `Ctrl+S` saves, `Ctrl+Shift+S` saves as +- Drag a Markdown file into the window to open it +- `Ctrl+1` – `Ctrl+6` set heading levels, `Ctrl+/` wraps a code block +- `Ctrl+E` exports the document to PDF (A4) + +## Modes + +The default view is the editor: Markdown markers are hidden, headings are sized, +and the line under the cursor reveals its raw markers. +Click `` in the status bar to see the source with line numbers. + +## Theme + +Follows the system light / dark setting by default; +switch it under **View → Theme**. + +> Quotes, lists, `inline code`, **bold**, *italic*, ~~strikethrough~~ +> and [links](https://example.com) all render in place. + +- [x] Syntax highlighting +- [x] Folder sidebar +- [x] PDF export + +```cpp +#include + +int main() +{ + std::printf("Hello, VoiletMark!\n"); + return 0; +} +``` + +--- +)VOILETMARK"; + } // namespace MainWindow::MainWindow(QWidget *parent) @@ -109,6 +156,8 @@ MainWindow::MainWindow(QWidget *parent) this, &MainWindow::changeZoom); connect(m_editor, &MarkdownEditor::fileDropped, this, &MainWindow::openPath); + connect(LanguageManager::instance(), &LanguageManager::languageChanged, + this, &MainWindow::retranslateUi); showWelcomeDocument(); updateEditActionStates(); @@ -266,40 +315,40 @@ void MainWindow::createSidebar() void MainWindow::createMenus() { - QMenu *fileMenu = menuBar()->addMenu(tr("文件")); - fileMenu->addAction(m_newAction); - fileMenu->addAction(m_openAction); - fileMenu->addAction(m_openFolderAction); - m_recentMenu = fileMenu->addMenu(tr("最近打开")); - fileMenu->addSeparator(); - fileMenu->addAction(m_saveAction); - fileMenu->addAction(m_saveAsAction); - fileMenu->addAction(m_exportPdfAction); - fileMenu->addSeparator(); - fileMenu->addAction(m_quitAction); + m_fileMenu = menuBar()->addMenu(tr("文件")); + m_fileMenu->addAction(m_newAction); + m_fileMenu->addAction(m_openAction); + m_fileMenu->addAction(m_openFolderAction); + m_recentMenu = m_fileMenu->addMenu(tr("最近打开")); + m_fileMenu->addSeparator(); + m_fileMenu->addAction(m_saveAction); + m_fileMenu->addAction(m_saveAsAction); + m_fileMenu->addAction(m_exportPdfAction); + m_fileMenu->addSeparator(); + m_fileMenu->addAction(m_quitAction); - QMenu *editMenu = menuBar()->addMenu(tr("编辑")); - editMenu->addAction(m_undoAction); - editMenu->addAction(m_redoAction); - editMenu->addSeparator(); - editMenu->addAction(m_cutAction); - editMenu->addAction(m_copyAction); - editMenu->addAction(m_pasteAction); - editMenu->addSeparator(); - editMenu->addAction(m_selectAllAction); + m_editMenu = menuBar()->addMenu(tr("编辑")); + m_editMenu->addAction(m_undoAction); + m_editMenu->addAction(m_redoAction); + m_editMenu->addSeparator(); + m_editMenu->addAction(m_cutAction); + m_editMenu->addAction(m_copyAction); + m_editMenu->addAction(m_pasteAction); + m_editMenu->addSeparator(); + m_editMenu->addAction(m_selectAllAction); - QMenu *viewMenu = menuBar()->addMenu(tr("视图")); - viewMenu->addAction(m_sidebarAction); - viewMenu->addAction(m_sourceModeAction); - viewMenu->addSeparator(); - viewMenu->addAction(m_wrapAction); - viewMenu->addSeparator(); - viewMenu->addAction(m_zoomInAction); - viewMenu->addAction(m_zoomOutAction); - viewMenu->addAction(m_zoomResetAction); - viewMenu->addSeparator(); + m_viewMenu = menuBar()->addMenu(tr("视图")); + m_viewMenu->addAction(m_sidebarAction); + m_viewMenu->addAction(m_sourceModeAction); + m_viewMenu->addSeparator(); + m_viewMenu->addAction(m_wrapAction); + m_viewMenu->addSeparator(); + m_viewMenu->addAction(m_zoomInAction); + m_viewMenu->addAction(m_zoomOutAction); + m_viewMenu->addAction(m_zoomResetAction); + m_viewMenu->addSeparator(); - QMenu *themeMenu = viewMenu->addMenu(tr("主题")); + m_themeMenu = m_viewMenu->addMenu(tr("主题")); QActionGroup *themeGroup = new QActionGroup(this); themeGroup->setExclusive(true); @@ -312,7 +361,7 @@ void MainWindow::createMenus() m_themeScheduleAction}) { action->setCheckable(true); themeGroup->addAction(action); - themeMenu->addAction(action); + m_themeMenu->addAction(action); } m_themeSystemAction->setChecked(true); @@ -325,16 +374,33 @@ void MainWindow::createMenus() connect(m_themeScheduleAction, &QAction::triggered, this, [this] { applyThemeMode(static_cast(ThemeManager::Mode::Schedule)); }); - QMenu *formatMenu = menuBar()->addMenu(tr("格式")); - QMenu *headingMenu = formatMenu->addMenu(tr("标题")); + m_languageMenu = m_viewMenu->addMenu(tr("语言")); + QActionGroup *languageGroup = new QActionGroup(this); + languageGroup->setExclusive(true); + m_languageChineseAction = + new QAction(LanguageManager::displayName(QStringLiteral("zh_CN")), this); + m_languageEnglishAction = + new QAction(LanguageManager::displayName(QStringLiteral("en")), this); + for (QAction *action : {m_languageChineseAction, m_languageEnglishAction}) { + action->setCheckable(true); + languageGroup->addAction(action); + m_languageMenu->addAction(action); + } + connect(m_languageChineseAction, &QAction::triggered, this, + [this] { applyLanguage(QStringLiteral("zh_CN")); }); + connect(m_languageEnglishAction, &QAction::triggered, this, + [this] { applyLanguage(QStringLiteral("en")); }); + + m_formatMenu = menuBar()->addMenu(tr("格式")); + m_headingMenu = m_formatMenu->addMenu(tr("标题")); for (QAction *headingAction : m_headingActions) { if (headingAction) - headingMenu->addAction(headingAction); + m_headingMenu->addAction(headingAction); } - formatMenu->addAction(m_codeBlockAction); + m_formatMenu->addAction(m_codeBlockAction); - QMenu *helpMenu = menuBar()->addMenu(tr("帮助")); - helpMenu->addAction(tr("关于 VoiletMark"), this, &MainWindow::showAbout); + m_helpMenu = menuBar()->addMenu(tr("帮助")); + m_aboutAction = m_helpMenu->addAction(tr("关于 VoiletMark"), this, &MainWindow::showAbout); } void MainWindow::createStatusBar() @@ -446,9 +512,11 @@ QString MainWindow::startDirectory() const void MainWindow::showWelcomeDocument() { - QString text = QString::fromUtf8(kWelcomeText); + const bool english = LanguageManager::instance()->language() == QLatin1String("en"); + QString text = QString::fromUtf8(english ? kWelcomeTextEn : kWelcomeText); if (text.startsWith(QLatin1Char('\n'))) text.remove(0, 1); + m_welcomeText = text; m_editor->setPlainText(text); m_editor->applyBlockRhythm(); m_editor->document()->setModified(false); @@ -544,8 +612,37 @@ bool MainWindow::saveFileAs() void MainWindow::exportPdf() { - ExportDialog dialog(m_editor->toPlainText(), displayName(), startDirectory(), this); - dialog.exec(); + QString suggested = m_currentPath.isEmpty() ? tr("未命名") + : QFileInfo(m_currentPath).completeBaseName(); + suggested += QStringLiteral(".pdf"); + + QString target = QFileDialog::getSaveFileName( + this, tr("导出 PDF"), QDir(startDirectory()).filePath(suggested), + tr("PDF 文件 (*.pdf)")); + if (target.isEmpty()) + return; + if (!target.endsWith(QStringLiteral(".pdf"), Qt::CaseInsensitive)) + target += QStringLiteral(".pdf"); + + QTextDocument document; + MarkdownRenderer::render(m_editor->toPlainText(), &document); + document.setPageSize(QSizeF(672.76, 1001.57)); + + QPrinter printer(QPrinter::HighResolution); + printer.setOutputFormat(QPrinter::PdfFormat); + printer.setOutputFileName(target); + printer.setPageSize(QPageSize(QPageSize::A4)); + printer.setPageMargins(QMarginsF(16, 16, 16, 16), QPageLayout::Millimeter); + printer.setDocName(QFileInfo(target).completeBaseName()); + document.print(&printer); + + if (!QFileInfo::exists(target) || QFileInfo(target).size() <= 0) { + QMessageBox::warning(this, tr("导出失败"), + tr("没能写出「%1」,换个位置再试一次。") + .arg(QDir::toNativeSeparators(target))); + return; + } + statusBar()->showMessage(tr("已导出 %1").arg(QDir::toNativeSeparators(target)), 4000); } bool MainWindow::writeToFile(const QString &path) @@ -667,6 +764,83 @@ void MainWindow::applyThemeMode(int mode) ThemeManager::instance()->setMode(static_cast(mode), true); } +void MainWindow::applyLanguage(const QString &code) +{ + LanguageManager::instance()->setLanguage(code); +} + +void MainWindow::retranslateUi() +{ + m_fileMenu->setTitle(tr("文件")); + m_editMenu->setTitle(tr("编辑")); + m_viewMenu->setTitle(tr("视图")); + m_formatMenu->setTitle(tr("格式")); + m_helpMenu->setTitle(tr("帮助")); + m_recentMenu->setTitle(tr("最近打开")); + m_themeMenu->setTitle(tr("主题")); + m_headingMenu->setTitle(tr("标题")); + m_languageMenu->setTitle(tr("语言")); + + m_newAction->setText(tr("新建")); + m_openAction->setText(tr("打开…")); + m_openFolderAction->setText(tr("打开文件夹…")); + m_saveAction->setText(tr("保存")); + m_saveAsAction->setText(tr("另存为…")); + m_exportPdfAction->setText(tr("导出 PDF…")); + m_quitAction->setText(tr("退出")); + + m_undoAction->setText(tr("撤销")); + m_redoAction->setText(tr("重做")); + m_cutAction->setText(tr("剪切")); + m_copyAction->setText(tr("复制")); + m_pasteAction->setText(tr("粘贴")); + m_selectAllAction->setText(tr("全选")); + + m_sidebarAction->setText(tr("侧栏")); + m_sourceModeAction->setText(tr("源代码模式")); + m_wrapAction->setText(tr("自动换行")); + m_zoomInAction->setText(tr("放大")); + m_zoomOutAction->setText(tr("缩小")); + m_zoomResetAction->setText(tr("实际大小")); + + m_themeSystemAction->setText(ThemeManager::modeLabel(ThemeManager::Mode::System)); + m_themeLightAction->setText(ThemeManager::modeLabel(ThemeManager::Mode::Light)); + m_themeDarkAction->setText(ThemeManager::modeLabel(ThemeManager::Mode::Dark)); + m_themeScheduleAction->setText(ThemeManager::modeLabel(ThemeManager::Mode::Schedule)); + m_themeScheduleAction->setToolTip(tr("6:00–18:00 使用浅色,其余时间使用深色")); + + for (int level = 1; level <= 6; ++level) { + if (m_headingActions[level - 1]) + m_headingActions[level - 1]->setText(tr("标题 %1").arg(level)); + } + m_codeBlockAction->setText(tr("代码块")); + m_aboutAction->setText(tr("关于 VoiletMark")); + + if (m_sidebarButton) { + m_sidebarButton->setText(tr("侧栏")); + m_sidebarButton->setToolTip(tr("显示 / 隐藏文件夹侧栏(Ctrl+B)")); + } + if (m_sourceButton) + m_sourceButton->setToolTip(tr("源代码模式:关闭语法高亮与行号(Ctrl+Shift+M)")); + + const QString language = LanguageManager::instance()->language(); + m_languageChineseAction->setChecked(language == QLatin1String("zh_CN")); + m_languageEnglishAction->setChecked(language == QLatin1String("en")); + + // 侧栏、最近打开、欢迎文档都跟着换 + m_displayedFolder.clear(); + refreshRecentMenu(); + refreshFolder(); + + if (m_currentPath.isEmpty() && !m_editor->document()->isModified() + && m_editor->toPlainText() == m_welcomeText) { + showWelcomeDocument(); + } + + updateWindowTitle(); + updateStatusBar(); +} + void MainWindow::showAbout() { QMessageBox box(this); diff --git a/src/MainWindow.h b/src/MainWindow.h index 361c68d..2dc98b3 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -62,6 +62,8 @@ private: void resetZoom(); void setWordWrapEnabled(bool enabled); void applyThemeMode(int mode); + void applyLanguage(const QString &code); + void retranslateUi(); void showAbout(); void updateStatusBar(); @@ -120,4 +122,18 @@ private: QAction *m_headingActions[6] = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; QMenu *m_recentMenu = nullptr; + QMenu *m_fileMenu = nullptr; + QMenu *m_editMenu = nullptr; + QMenu *m_viewMenu = nullptr; + QMenu *m_formatMenu = nullptr; + QMenu *m_helpMenu = nullptr; + QMenu *m_themeMenu = nullptr; + QMenu *m_headingMenu = nullptr; + QMenu *m_languageMenu = nullptr; + + QAction *m_aboutAction = nullptr; + QAction *m_languageChineseAction = nullptr; + QAction *m_languageEnglishAction = nullptr; + + QString m_welcomeText; }; diff --git a/src/main.cpp b/src/main.cpp index c80aa85..ae4585b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include "LanguageManager.h" #include "MainWindow.h" #include "ThemeManager.h" @@ -51,6 +52,7 @@ int main(int argc, char *argv[]) mode = ThemeManager::Mode::System; } ThemeManager::instance()->initialize(mode, false); + LanguageManager::instance()->initialize(); MainWindow window; window.show(); diff --git a/translations/qtbase_zh_CN.qm b/translations/qtbase_zh_CN.qm new file mode 100644 index 0000000..ba2273c Binary files /dev/null and b/translations/qtbase_zh_CN.qm differ diff --git a/translations/voiletmark_en.qm b/translations/voiletmark_en.qm new file mode 100644 index 0000000..5632f97 Binary files /dev/null and b/translations/voiletmark_en.qm differ diff --git a/translations/voiletmark_en.ts b/translations/voiletmark_en.ts new file mode 100644 index 0000000..35e7dca --- /dev/null +++ b/translations/voiletmark_en.ts @@ -0,0 +1,395 @@ + + + + MainWindow + + + + 新建 + New + + + + + 打开… + Open… + + + + + 保存 + Save + + + + + 另存为… + Save As… + + + + + 导出 PDF… + Export PDF… + + + + + 退出 + Quit + + + + + 撤销 + Undo + + + + + 重做 + Redo + + + + + 剪切 + Cut + + + + + 复制 + Copy + + + + + 粘贴 + Paste + + + + + 全选 + Select All + + + + + 自动换行 + Word Wrap + + + + + 放大 + Zoom In + + + + + 缩小 + Zoom Out + + + + + 实际大小 + Actual Size + + + + + 打开文件夹… + Open Folder… + + + + + + + 侧栏 + Sidebar + + + + + 源代码模式 + Source Mode + + + + + 代码块 + Code Block + + + + + 标题 %1 + Heading %1 + + + + 文件夹 + Folder + + + + + 未打开文件夹 + No folder open + + + + + 文件 + File + + + + + 最近打开 + Recent Files + + + + + 编辑 + Edit + + + + + 视图 + View + + + + + 主题 + Theme + + + + + 6:00–18:00 使用浅色,其余时间使用深色 + Light from 6:00 to 18:00, dark the rest of the day + + + + + 语言 + Language + + + + + 格式 + Format + + + + + 标题 + Heading + + + + + 帮助 + Help + + + + + + 关于 VoiletMark + About VoiletMark + + + + + 显示 / 隐藏文件夹侧栏(Ctrl+B) + Show / hide the folder sidebar (Ctrl+B) + + + + + 源代码模式:关闭语法高亮与行号(Ctrl+Shift+M) + Source mode: turn off highlighting and line numbers (Ctrl+Shift+M) + + + + + 未命名 + Untitled + + + + 打开 Markdown 文件 + Open Markdown File + + + + Markdown 文件 (*.md *.markdown *.mdown *.mkd);;文本文件 (*.txt);;所有文件 (*) + Markdown files (*.md *.markdown *.mdown *.mkd);;Text files (*.txt);;All files (*) + + + + 无法打开文件 + Cannot Open File + + + + 读取「%1」失败:%2 + Could not read "%1": %2 + + + + 另存为 + Save As + + + + Markdown 文件 (*.md);;文本文件 (*.txt);;所有文件 (*) + Markdown files (*.md);;Text files (*.txt);;All files (*) + + + + 导出 PDF + Export PDF + + + + PDF 文件 (*.pdf) + PDF files (*.pdf) + + + + 导出失败 + Export Failed + + + + 没能写出「%1」,换个位置再试一次。 + Could not write "%1". Try another location. + + + + 已导出 %1 + Exported to %1 + + + + + + 无法保存文件 + Cannot Save File + + + + 写入「%1」失败:%2 + Could not write "%1": %2 + + + + 写入「%1」时发生错误。 + An error occurred while writing "%1". + + + + 提交「%1」失败:%2 + Could not commit "%1": %2 + + + + 已保存 %1 + Saved to %1 + + + + VoiletMark + VoiletMark + + + + 「%1」有未保存的修改,是否保存? + "%1" has unsaved changes. Save them? + + + + %1[*] — VoiletMark + %1[*] — VoiletMark + + + + 行 %1,列 %2 + Line %1, Col %2 + + + + 已选 %1 字 + %1 selected + + + + %1 字 + %1 chars + + + + <h3>VoiletMark %1</h3><p>一个轻量的跨平台 Markdown 编辑器。</p><p>基于 Qt %2 与 C++17 构建。</p> + <h3>VoiletMark %1</h3><p>A lightweight cross-platform Markdown editor.</p><p>Built with Qt %2 and C++17.</p> + + + + (暂无记录) + (none) + + + + 清空列表 + Clear List + + + + 打开文件夹 + Open Folder + + + + 文件 → 打开文件夹… + File → Open Folder… + + + + (没有 Markdown 文件) + (no Markdown files) + + + + QObject + + + 浅色 + Light + + + + 深色 + Dark + + + + 按时段自动 + Scheduled + + + + 跟随系统 + Follow System + + + \ No newline at end of file