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 @@

-导出 PDF 前先给一个预览窗口,确认无误再选路径保存:
-
-
-
## 功能
### 编辑
@@ -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