feat: v0.6.0 —— 选中语言(中/英)即时切换,导出不再有预览窗口

导出
- 去掉导出预览对话框:Ctrl+E 直接弹保存对话框,选路径即导出
- 导出失败才弹提示,成功只在状态栏显示一行

多语言
- 新增 LanguageManager:中文(zh_CN)/ 英文(en),视图 → 语言 切换,
  立即生效并记住选择
- 英文翻译由 translations/voiletmark_en.ts(69 条)编译成 .qm 内嵌
- 打包 Qt 官方的 qtbase_zh_CN.qm,文件对话框 / 消息框的按钮也跟着变中文
- 欢迎文档随语言切换(新增英文版)
- retranslateUi 负责菜单、动作、状态栏、侧栏文案的即时刷新

其它
- 删除 ExportDialog 与对应截图,README 同步
This commit is contained in:
2026-10-22 18:07:31 +08:00
parent 2f8726ad47
commit 5d1fb0fab7
14 changed files with 766 additions and 189 deletions
+13 -9
View File
@@ -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 级标题(再按取消) |
+3 -3
View File
@@ -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 之后能在应用菜单里找到
Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

+2
View File
@@ -1,5 +1,7 @@
<RCC>
<qresource prefix="/">
<file>packaging/voiletmark.png</file>
<file>translations/voiletmark_en.qm</file>
<file>translations/qtbase_zh_CN.qm</file>
</qresource>
</RCC>
-111
View File
@@ -1,111 +0,0 @@
#include "ExportDialog.h"
#include "MarkdownRenderer.h"
#include <QDir>
#include <QFileDialog>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <QPageSize>
#include <QPrinter>
#include <QPushButton>
#include <QScreen>
#include <QTextBrowser>
#include <QTextDocument>
#include <QVBoxLayout>
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();
}
-24
View File
@@ -1,24 +0,0 @@
#pragma once
#include <QDialog>
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;
};
+82
View File
@@ -0,0 +1,82 @@
#include "LanguageManager.h"
#include <QCoreApplication>
#include <QLocale>
#include <QSettings>
#include <QTranslator>
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();
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <QObject>
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;
};
+216 -42
View File
@@ -1,7 +1,8 @@
#include "MainWindow.h"
#include "ExportDialog.h"
#include "LanguageManager.h"
#include "MarkdownEditor.h"
#include "MarkdownRenderer.h"
#include "ThemeManager.h"
#include <QAction>
@@ -21,7 +22,10 @@
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QPageLayout>
#include <QPageSize>
#include <QPixmap>
#include <QPrinter>
#include <QSaveFile>
#include <QSettings>
#include <QSignalBlocker>
@@ -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 `<src>` 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 <cstdio>
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<int>(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<ThemeManager::Mode>(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);
+16
View File
@@ -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;
};
+2
View File
@@ -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();
Binary file not shown.
Binary file not shown.
+395
View File
@@ -0,0 +1,395 @@
<?xml version='1.0' encoding='utf-8'?>
<TS version="2.1" language="en_US">
<context>
<name>MainWindow</name>
<message>
<location filename="../src/MainWindow.cpp" line="167" />
<location filename="../src/MainWindow.cpp" line="781" />
<source>新建</source>
<translation>New</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="171" />
<location filename="../src/MainWindow.cpp" line="782" />
<source>打开…</source>
<translation>Open…</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="175" />
<location filename="../src/MainWindow.cpp" line="784" />
<source>保存</source>
<translation>Save</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="179" />
<location filename="../src/MainWindow.cpp" line="785" />
<source>另存为…</source>
<translation>Save As…</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="183" />
<location filename="../src/MainWindow.cpp" line="786" />
<source>导出 PDF…</source>
<translation>Export PDF…</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="187" />
<location filename="../src/MainWindow.cpp" line="787" />
<source>退出</source>
<translation>Quit</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="191" />
<location filename="../src/MainWindow.cpp" line="789" />
<source>撤销</source>
<translation>Undo</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="195" />
<location filename="../src/MainWindow.cpp" line="790" />
<source>重做</source>
<translation>Redo</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="199" />
<location filename="../src/MainWindow.cpp" line="791" />
<source>剪切</source>
<translation>Cut</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="203" />
<location filename="../src/MainWindow.cpp" line="792" />
<source>复制</source>
<translation>Copy</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="207" />
<location filename="../src/MainWindow.cpp" line="793" />
<source>粘贴</source>
<translation>Paste</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="211" />
<location filename="../src/MainWindow.cpp" line="794" />
<source>全选</source>
<translation>Select All</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="215" />
<location filename="../src/MainWindow.cpp" line="798" />
<source>自动换行</source>
<translation>Word Wrap</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="220" />
<location filename="../src/MainWindow.cpp" line="799" />
<source>放大</source>
<translation>Zoom In</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="224" />
<location filename="../src/MainWindow.cpp" line="800" />
<source>缩小</source>
<translation>Zoom Out</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="228" />
<location filename="../src/MainWindow.cpp" line="801" />
<source>实际大小</source>
<translation>Actual Size</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="237" />
<location filename="../src/MainWindow.cpp" line="783" />
<source>打开文件夹…</source>
<translation>Open Folder…</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="241" />
<location filename="../src/MainWindow.cpp" line="407" />
<location filename="../src/MainWindow.cpp" line="796" />
<location filename="../src/MainWindow.cpp" line="817" />
<source>侧栏</source>
<translation>Sidebar</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="255" />
<location filename="../src/MainWindow.cpp" line="797" />
<source>源代码模式</source>
<translation>Source Mode</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="260" />
<location filename="../src/MainWindow.cpp" line="813" />
<source>代码块</source>
<translation>Code Block</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="267" />
<location filename="../src/MainWindow.cpp" line="811" />
<source>标题 %1</source>
<translation>Heading %1</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="278" />
<source>文件夹</source>
<translation>Folder</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="289" />
<location filename="../src/MainWindow.cpp" line="961" />
<source>未打开文件夹</source>
<translation>No folder open</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="315" />
<location filename="../src/MainWindow.cpp" line="771" />
<source>文件</source>
<translation>File</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="319" />
<location filename="../src/MainWindow.cpp" line="776" />
<source>最近打开</source>
<translation>Recent Files</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="327" />
<location filename="../src/MainWindow.cpp" line="772" />
<source>编辑</source>
<translation>Edit</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="337" />
<location filename="../src/MainWindow.cpp" line="773" />
<source>视图</source>
<translation>View</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="348" />
<location filename="../src/MainWindow.cpp" line="777" />
<source>主题</source>
<translation>Theme</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="356" />
<location filename="../src/MainWindow.cpp" line="807" />
<source>6:00–18:00 使用浅色,其余时间使用深色</source>
<translation>Light from 6:00 to 18:00, dark the rest of the day</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="374" />
<location filename="../src/MainWindow.cpp" line="779" />
<source>语言</source>
<translation>Language</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="391" />
<location filename="../src/MainWindow.cpp" line="774" />
<source>格式</source>
<translation>Format</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="392" />
<location filename="../src/MainWindow.cpp" line="778" />
<source>标题</source>
<translation>Heading</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="399" />
<location filename="../src/MainWindow.cpp" line="775" />
<source>帮助</source>
<translation>Help</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="400" />
<location filename="../src/MainWindow.cpp" line="814" />
<location filename="../src/MainWindow.cpp" line="844" />
<source>关于 VoiletMark</source>
<translation>About VoiletMark</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="411" />
<location filename="../src/MainWindow.cpp" line="818" />
<source>显示 / 隐藏文件夹侧栏(Ctrl+B)</source>
<translation>Show / hide the folder sidebar (Ctrl+B)</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="419" />
<location filename="../src/MainWindow.cpp" line="821" />
<source>源代码模式:关闭语法高亮与行号(Ctrl+Shift+M)</source>
<translation>Source mode: turn off highlighting and line numbers (Ctrl+Shift+M)</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="497" />
<location filename="../src/MainWindow.cpp" line="612" />
<source>未命名</source>
<translation>Untitled</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="546" />
<source>打开 Markdown 文件</source>
<translation>Open Markdown File</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="547" />
<source>Markdown 文件 (*.md *.markdown *.mdown *.mkd);;文本文件 (*.txt);;所有文件 (*)</source>
<translation>Markdown files (*.md *.markdown *.mdown *.mkd);;Text files (*.txt);;All files (*)</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="564" />
<source>无法打开文件</source>
<translation>Cannot Open File</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="565" />
<source>读取「%1」失败:%2</source>
<translation>Could not read "%1": %2</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="599" />
<source>另存为</source>
<translation>Save As</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="600" />
<source>Markdown 文件 (*.md);;文本文件 (*.txt);;所有文件 (*)</source>
<translation>Markdown files (*.md);;Text files (*.txt);;All files (*)</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="617" />
<source>导出 PDF</source>
<translation>Export PDF</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="618" />
<source>PDF 文件 (*.pdf)</source>
<translation>PDF files (*.pdf)</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="637" />
<source>导出失败</source>
<translation>Export Failed</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="638" />
<source>没能写出「%1」,换个位置再试一次。</source>
<translation>Could not write "%1". Try another location.</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="642" />
<source>已导出 %1</source>
<translation>Exported to %1</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="657" />
<location filename="../src/MainWindow.cpp" line="664" />
<location filename="../src/MainWindow.cpp" line="669" />
<source>无法保存文件</source>
<translation>Cannot Save File</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="658" />
<source>写入「%1」失败:%2</source>
<translation>Could not write "%1": %2</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="665" />
<source>写入「%1」时发生错误。</source>
<translation>An error occurred while writing "%1".</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="670" />
<source>提交「%1」失败:%2</source>
<translation>Could not commit "%1": %2</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="678" />
<source>已保存 %1</source>
<translation>Saved to %1</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="688" />
<source>VoiletMark</source>
<translation>VoiletMark</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="689" />
<source>「%1」有未保存的修改,是否保存?</source>
<translation>"%1" has unsaved changes. Save them?</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="713" />
<source>%1[*] — VoiletMark</source>
<translation>%1[*] — VoiletMark</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="732" />
<source>行 %1,列 %2</source>
<translation>Line %1, Col %2</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="734" />
<source> 已选 %1 字</source>
<translation> %1 selected</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="735" />
<source> %1 字</source>
<translation> %1 chars</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="848" />
<source>&lt;h3&gt;VoiletMark %1&lt;/h3&gt;&lt;p&gt;一个轻量的跨平台 Markdown 编辑器。&lt;/p&gt;&lt;p&gt;基于 Qt %2 与 C++17 构建。&lt;/p&gt;</source>
<translation>&lt;h3&gt;VoiletMark %1&lt;/h3&gt;&lt;p&gt;A lightweight cross-platform Markdown editor.&lt;/p&gt;&lt;p&gt;Built with Qt %2 and C++17.&lt;/p&gt;</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="883" />
<source>(暂无记录)</source>
<translation>(none)</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="896" />
<source>清空列表</source>
<translation>Clear List</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="906" />
<source>打开文件夹</source>
<translation>Open Folder</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="962" />
<source>文件 → 打开文件夹…</source>
<translation>File → Open Folder…</translation>
</message>
<message>
<location filename="../src/MainWindow.cpp" line="998" />
<source>(没有 Markdown 文件)</source>
<translation>(no Markdown files)</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<location filename="../src/ThemeManager.cpp" line="354" />
<source>浅色</source>
<translation>Light</translation>
</message>
<message>
<location filename="../src/ThemeManager.cpp" line="355" />
<source>深色</source>
<translation>Dark</translation>
</message>
<message>
<location filename="../src/ThemeManager.cpp" line="356" />
<source>按时段自动</source>
<translation>Scheduled</translation>
</message>
<message>
<location filename="../src/ThemeManager.cpp" line="359" />
<source>跟随系统</source>
<translation>Follow System</translation>
</message>
</context>
</TS>