rgpdf 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rgpdf-0.0.1/PKG-INFO +75 -0
- rgpdf-0.0.1/README.md +62 -0
- rgpdf-0.0.1/pyproject.toml +33 -0
- rgpdf-0.0.1/src/rgpdf/__init__.py +3 -0
- rgpdf-0.0.1/src/rgpdf/__main__.py +6 -0
- rgpdf-0.0.1/src/rgpdf/app.py +163 -0
- rgpdf-0.0.1/src/rgpdf/assets/__init__.py +2 -0
- rgpdf-0.0.1/src/rgpdf/assets/rgpdf.svg +11 -0
- rgpdf-0.0.1/src/rgpdf/i18n.py +114 -0
- rgpdf-0.0.1/src/rgpdf/main_window.py +754 -0
- rgpdf-0.0.1/src/rgpdf/models.py +69 -0
- rgpdf-0.0.1/src/rgpdf/preview.py +271 -0
- rgpdf-0.0.1/src/rgpdf/resources.py +11 -0
- rgpdf-0.0.1/src/rgpdf/search.py +275 -0
- rgpdf-0.0.1/src/rgpdf/worker.py +44 -0
rgpdf-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: rgpdf
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Cross-platform PDF text and regular-expression search desktop app
|
|
5
|
+
Requires-Dist: pymupdf>=1.25
|
|
6
|
+
Requires-Dist: pyside6>=6.8
|
|
7
|
+
Requires-Dist: regex>=2024.11.6
|
|
8
|
+
Requires-Python: >=3.12, <3.14
|
|
9
|
+
Project-URL: Homepage, https://github.com/codemee/rgpdf
|
|
10
|
+
Project-URL: Issues, https://github.com/codemee/rgpdf/issues
|
|
11
|
+
Project-URL: Repository, https://github.com/codemee/rgpdf
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# rgpdf
|
|
15
|
+
|
|
16
|
+
[繁體中文](README.zh-TW.md) · English
|
|
17
|
+
|
|
18
|
+
rgpdf is a cross-platform desktop application for searching text-layer PDFs with plain text or regular expressions. Matches appear as they are found and can be opened in an asynchronous page preview with precise, configurable highlighting.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- Search multiple PDFs in a folder, optionally including subfolders.
|
|
23
|
+
- Plain-text and regular-expression modes with optional case sensitivity.
|
|
24
|
+
- Matches across lines and page boundaries, including line-end dehyphenation.
|
|
25
|
+
- Incremental per-match results without blocking the interface.
|
|
26
|
+
- Three coordinated views for files, matching passages, and PDF page preview.
|
|
27
|
+
- Per-character highlight geometry and configurable highlight colors.
|
|
28
|
+
- English and Traditional Chinese interfaces, plus system/light/dark themes.
|
|
29
|
+
- Persistent search and appearance preferences.
|
|
30
|
+
|
|
31
|
+
rgpdf searches existing PDF text layers. It does not perform OCR or modify source files.
|
|
32
|
+
|
|
33
|
+
## Install the latest release
|
|
34
|
+
|
|
35
|
+
Install [uv](https://docs.astral.sh/uv/), then install the latest release from PyPI:
|
|
36
|
+
|
|
37
|
+
```shell
|
|
38
|
+
uv tool install rgpdf
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Run the application:
|
|
42
|
+
|
|
43
|
+
```shell
|
|
44
|
+
rgpdf
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
To update an existing installation:
|
|
48
|
+
|
|
49
|
+
```shell
|
|
50
|
+
uv tool upgrade rgpdf
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Windows and macOS are supported; uv manages the required Python environment.
|
|
54
|
+
|
|
55
|
+
To install the latest source release directly from GitHub instead, use the moving `latest` tag:
|
|
56
|
+
|
|
57
|
+
```shell
|
|
58
|
+
uv tool install "git+https://github.com/codemee/rgpdf.git@latest"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
```shell
|
|
64
|
+
git clone https://github.com/codemee/rgpdf.git
|
|
65
|
+
cd rgpdf
|
|
66
|
+
uv sync
|
|
67
|
+
uv run pytest
|
|
68
|
+
uv run rgpdf
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
See [Technical details](docs/TECHNICAL.md) for architecture, matching semantics, concurrency, and release engineering. Version history is in the [changelog](CHANGELOG.md).
|
|
72
|
+
|
|
73
|
+
## Releases
|
|
74
|
+
|
|
75
|
+
Immutable releases use version tags such as `v0.0.1`. The movable `latest` Git tag always points to the newest published source release. PyPI provides the canonical packaged release.
|
rgpdf-0.0.1/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# rgpdf
|
|
2
|
+
|
|
3
|
+
[繁體中文](README.zh-TW.md) · English
|
|
4
|
+
|
|
5
|
+
rgpdf is a cross-platform desktop application for searching text-layer PDFs with plain text or regular expressions. Matches appear as they are found and can be opened in an asynchronous page preview with precise, configurable highlighting.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Search multiple PDFs in a folder, optionally including subfolders.
|
|
10
|
+
- Plain-text and regular-expression modes with optional case sensitivity.
|
|
11
|
+
- Matches across lines and page boundaries, including line-end dehyphenation.
|
|
12
|
+
- Incremental per-match results without blocking the interface.
|
|
13
|
+
- Three coordinated views for files, matching passages, and PDF page preview.
|
|
14
|
+
- Per-character highlight geometry and configurable highlight colors.
|
|
15
|
+
- English and Traditional Chinese interfaces, plus system/light/dark themes.
|
|
16
|
+
- Persistent search and appearance preferences.
|
|
17
|
+
|
|
18
|
+
rgpdf searches existing PDF text layers. It does not perform OCR or modify source files.
|
|
19
|
+
|
|
20
|
+
## Install the latest release
|
|
21
|
+
|
|
22
|
+
Install [uv](https://docs.astral.sh/uv/), then install the latest release from PyPI:
|
|
23
|
+
|
|
24
|
+
```shell
|
|
25
|
+
uv tool install rgpdf
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Run the application:
|
|
29
|
+
|
|
30
|
+
```shell
|
|
31
|
+
rgpdf
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
To update an existing installation:
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
uv tool upgrade rgpdf
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Windows and macOS are supported; uv manages the required Python environment.
|
|
41
|
+
|
|
42
|
+
To install the latest source release directly from GitHub instead, use the moving `latest` tag:
|
|
43
|
+
|
|
44
|
+
```shell
|
|
45
|
+
uv tool install "git+https://github.com/codemee/rgpdf.git@latest"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Development
|
|
49
|
+
|
|
50
|
+
```shell
|
|
51
|
+
git clone https://github.com/codemee/rgpdf.git
|
|
52
|
+
cd rgpdf
|
|
53
|
+
uv sync
|
|
54
|
+
uv run pytest
|
|
55
|
+
uv run rgpdf
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
See [Technical details](docs/TECHNICAL.md) for architecture, matching semantics, concurrency, and release engineering. Version history is in the [changelog](CHANGELOG.md).
|
|
59
|
+
|
|
60
|
+
## Releases
|
|
61
|
+
|
|
62
|
+
Immutable releases use version tags such as `v0.0.1`. The movable `latest` Git tag always points to the newest published source release. PyPI provides the canonical packaged release.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rgpdf"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "Cross-platform PDF text and regular-expression search desktop app"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12,<3.14"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"pymupdf>=1.25",
|
|
9
|
+
"pyside6>=6.8",
|
|
10
|
+
"regex>=2024.11.6",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.urls]
|
|
14
|
+
Homepage = "https://github.com/codemee/rgpdf"
|
|
15
|
+
Repository = "https://github.com/codemee/rgpdf"
|
|
16
|
+
Issues = "https://github.com/codemee/rgpdf/issues"
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
rgpdf = "rgpdf.app:main"
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=8.3",
|
|
24
|
+
"pytest-qt>=4.4",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["uv_build>=0.8,<0.9"]
|
|
29
|
+
build-backend = "uv_build"
|
|
30
|
+
|
|
31
|
+
[tool.pytest.ini_options]
|
|
32
|
+
testpaths = ["tests"]
|
|
33
|
+
addopts = "-q"
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import QSettings, Qt
|
|
6
|
+
from PySide6.QtGui import QColor, QPalette
|
|
7
|
+
from PySide6.QtWidgets import QApplication
|
|
8
|
+
|
|
9
|
+
from rgpdf.main_window import MainWindow
|
|
10
|
+
from rgpdf.resources import application_icon
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
BASE_STYLE = """
|
|
14
|
+
QWidget { font-size: 13px; color: palette(window-text); }
|
|
15
|
+
QMainWindow, QWidget#centralWidget, QMenuBar, QMenu { background: palette(window); }
|
|
16
|
+
QToolBar#settingsToolbar {
|
|
17
|
+
background: palette(alternate-base);
|
|
18
|
+
border: 0;
|
|
19
|
+
border-bottom: 1px solid palette(mid);
|
|
20
|
+
padding: 2px 7px;
|
|
21
|
+
spacing: 1px;
|
|
22
|
+
}
|
|
23
|
+
QToolBar#settingsToolbar::separator {
|
|
24
|
+
background: palette(mid);
|
|
25
|
+
width: 1px;
|
|
26
|
+
margin: 7px 6px;
|
|
27
|
+
}
|
|
28
|
+
QLineEdit, QTreeWidget, QScrollArea {
|
|
29
|
+
border: 1px solid palette(mid);
|
|
30
|
+
border-radius: 5px;
|
|
31
|
+
padding: 5px;
|
|
32
|
+
background: palette(base);
|
|
33
|
+
}
|
|
34
|
+
QToolButton {
|
|
35
|
+
border: 0;
|
|
36
|
+
border-radius: 8px;
|
|
37
|
+
background: transparent;
|
|
38
|
+
}
|
|
39
|
+
QToolButton:hover { background: palette(midlight); }
|
|
40
|
+
QToolButton:pressed { background: palette(mid); }
|
|
41
|
+
QToolButton:checked {
|
|
42
|
+
background: palette(midlight);
|
|
43
|
+
border: 1px solid palette(mid);
|
|
44
|
+
}
|
|
45
|
+
QPushButton {
|
|
46
|
+
border: 0;
|
|
47
|
+
border-radius: 7px;
|
|
48
|
+
padding: 6px 12px;
|
|
49
|
+
background: palette(button);
|
|
50
|
+
}
|
|
51
|
+
QPushButton:hover { background: palette(midlight); }
|
|
52
|
+
QPushButton:pressed { background: palette(midlight); }
|
|
53
|
+
QPushButton:disabled { color: palette(mid); }
|
|
54
|
+
QGroupBox {
|
|
55
|
+
border: 1px solid palette(mid);
|
|
56
|
+
border-radius: 7px;
|
|
57
|
+
margin-top: 10px;
|
|
58
|
+
padding-top: 7px;
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
}
|
|
61
|
+
QGroupBox::title { subcontrol-origin: margin; left: 9px; padding: 0 4px; }
|
|
62
|
+
QFrame#highlightPanel {
|
|
63
|
+
background: palette(alternate-base);
|
|
64
|
+
border: 1px solid palette(mid);
|
|
65
|
+
border-radius: 6px;
|
|
66
|
+
}
|
|
67
|
+
QFrame#searchArea {
|
|
68
|
+
background: palette(alternate-base);
|
|
69
|
+
border: 1px solid palette(mid);
|
|
70
|
+
border-radius: 9px;
|
|
71
|
+
}
|
|
72
|
+
QHeaderView::section {
|
|
73
|
+
background: palette(alternate-base);
|
|
74
|
+
color: palette(text);
|
|
75
|
+
border: 0;
|
|
76
|
+
border-bottom: 1px solid palette(mid);
|
|
77
|
+
padding: 5px 7px;
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
}
|
|
80
|
+
QProgressBar { border: 0; border-radius: 3px; text-align: center; min-width: 180px; }
|
|
81
|
+
QProgressBar::chunk { background: palette(highlight); border-radius: 3px; }
|
|
82
|
+
QSplitter::handle { background: transparent; }
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _light_palette(app: QApplication) -> QPalette:
|
|
87
|
+
palette = app.style().standardPalette()
|
|
88
|
+
palette.setColor(QPalette.ColorRole.Window, QColor("#f5f6f8"))
|
|
89
|
+
palette.setColor(QPalette.ColorRole.Base, QColor("#ffffff"))
|
|
90
|
+
palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#eef0f3"))
|
|
91
|
+
palette.setColor(QPalette.ColorRole.Button, QColor("#ffffff"))
|
|
92
|
+
palette.setColor(QPalette.ColorRole.ButtonText, QColor("#475569"))
|
|
93
|
+
palette.setColor(QPalette.ColorRole.Mid, QColor("#c8ced8"))
|
|
94
|
+
palette.setColor(QPalette.ColorRole.Midlight, QColor("#e2e6ed"))
|
|
95
|
+
palette.setColor(QPalette.ColorRole.PlaceholderText, QColor("#718096"))
|
|
96
|
+
palette.setColor(QPalette.ColorRole.Highlight, QColor("#3578e5"))
|
|
97
|
+
palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#ffffff"))
|
|
98
|
+
return palette
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _dark_palette() -> QPalette:
|
|
102
|
+
palette = QPalette()
|
|
103
|
+
palette.setColor(QPalette.ColorRole.Window, QColor("#202124"))
|
|
104
|
+
palette.setColor(QPalette.ColorRole.WindowText, QColor("#eceff4"))
|
|
105
|
+
palette.setColor(QPalette.ColorRole.Base, QColor("#292b2f"))
|
|
106
|
+
palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#32353a"))
|
|
107
|
+
palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#292b2f"))
|
|
108
|
+
palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#eceff4"))
|
|
109
|
+
palette.setColor(QPalette.ColorRole.Text, QColor("#eceff4"))
|
|
110
|
+
palette.setColor(QPalette.ColorRole.Button, QColor("#303236"))
|
|
111
|
+
palette.setColor(QPalette.ColorRole.ButtonText, QColor("#eceff4"))
|
|
112
|
+
palette.setColor(QPalette.ColorRole.BrightText, QColor("#ffffff"))
|
|
113
|
+
palette.setColor(QPalette.ColorRole.Link, QColor("#78a9ff"))
|
|
114
|
+
palette.setColor(QPalette.ColorRole.Highlight, QColor("#4f8ef7"))
|
|
115
|
+
palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#ffffff"))
|
|
116
|
+
palette.setColor(QPalette.ColorRole.Mid, QColor("#55585e"))
|
|
117
|
+
palette.setColor(QPalette.ColorRole.Midlight, QColor("#45484d"))
|
|
118
|
+
palette.setColor(QPalette.ColorRole.PlaceholderText, QColor("#aeb6c4"))
|
|
119
|
+
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, QColor("#777b82"))
|
|
120
|
+
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, QColor("#777b82"))
|
|
121
|
+
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, QColor("#777b82"))
|
|
122
|
+
return palette
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def apply_application_theme(app: QApplication, theme: str) -> None:
|
|
126
|
+
selected = theme
|
|
127
|
+
if theme == "system":
|
|
128
|
+
scheme = app.styleHints().colorScheme()
|
|
129
|
+
selected = "dark" if scheme == Qt.ColorScheme.Dark else "light"
|
|
130
|
+
app.setPalette(_dark_palette() if selected == "dark" else _light_palette(app))
|
|
131
|
+
# Qt caches palette() values referenced by stylesheets, so force a full repolish.
|
|
132
|
+
app.setStyleSheet("")
|
|
133
|
+
app.setStyleSheet(BASE_STYLE)
|
|
134
|
+
for widget in app.topLevelWidgets():
|
|
135
|
+
widget.style().unpolish(widget)
|
|
136
|
+
widget.style().polish(widget)
|
|
137
|
+
widget.update()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def main() -> int:
|
|
141
|
+
QApplication.setHighDpiScaleFactorRoundingPolicy(
|
|
142
|
+
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
|
|
143
|
+
)
|
|
144
|
+
app = QApplication(sys.argv)
|
|
145
|
+
app.setApplicationName("rgpdf")
|
|
146
|
+
app.setOrganizationName("rgpdf")
|
|
147
|
+
app.setWindowIcon(application_icon())
|
|
148
|
+
app.setStyle("Fusion")
|
|
149
|
+
app.setStyleSheet(BASE_STYLE)
|
|
150
|
+
|
|
151
|
+
def apply_theme(theme: str) -> None:
|
|
152
|
+
apply_application_theme(app, theme)
|
|
153
|
+
|
|
154
|
+
settings = QSettings("rgpdf", "rgpdf")
|
|
155
|
+
apply_theme(settings.value("appearance/theme", "system", type=str))
|
|
156
|
+
window = MainWindow(apply_theme)
|
|
157
|
+
app.styleHints().colorSchemeChanged.connect(
|
|
158
|
+
lambda _scheme: apply_theme("system")
|
|
159
|
+
if settings.value("appearance/theme", "system", type=str) == "system"
|
|
160
|
+
else None
|
|
161
|
+
)
|
|
162
|
+
window.show()
|
|
163
|
+
return app.exec()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Regular expression document search">
|
|
3
|
+
<path d="M120 36h190l92 92v318c0 20-16 36-36 36H120c-20 0-36-16-36-36V72c0-20 16-36 36-36z" fill="#315EFB"/>
|
|
4
|
+
<path d="M310 36v92h92z" fill="#BFD0FF"/>
|
|
5
|
+
<circle cx="180" cy="268" r="20" fill="#FFFFFF"/>
|
|
6
|
+
<g stroke="#FFFFFF" stroke-width="22" stroke-linecap="round">
|
|
7
|
+
<path d="M286 210v116"/>
|
|
8
|
+
<path d="M236 239l100 58"/>
|
|
9
|
+
<path d="M236 297l100-58"/>
|
|
10
|
+
</g>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
TRANSLATIONS: dict[str, dict[str, str]] = {
|
|
5
|
+
"zh_TW": {
|
|
6
|
+
"app_title": "rgpdf — PDF 搜尋",
|
|
7
|
+
"folder": "PDF 資料夾",
|
|
8
|
+
"browse": "瀏覽…",
|
|
9
|
+
"pattern": "搜尋樣式",
|
|
10
|
+
"pattern_hint": "輸入文字或正規表示式",
|
|
11
|
+
"plain": "純文字",
|
|
12
|
+
"regex": "規則表達式",
|
|
13
|
+
"case": "區分大小寫",
|
|
14
|
+
"recursive": "包含子資料夾",
|
|
15
|
+
"highlight_settings": "螢光筆顏色",
|
|
16
|
+
"common_colors": "常用顏色",
|
|
17
|
+
"custom_color": "自訂顏色…",
|
|
18
|
+
"choose_highlight_color": "選擇螢光筆顏色",
|
|
19
|
+
"settings": "設定",
|
|
20
|
+
"enabled": "啟用",
|
|
21
|
+
"disabled": "停用",
|
|
22
|
+
"search": "搜尋",
|
|
23
|
+
"cancel": "取消",
|
|
24
|
+
"files": "檔案",
|
|
25
|
+
"matches": "命中項目",
|
|
26
|
+
"filename": "檔名",
|
|
27
|
+
"count": "筆數",
|
|
28
|
+
"page_number": "頁碼",
|
|
29
|
+
"paragraph": "段落",
|
|
30
|
+
"preview": "頁面預覽",
|
|
31
|
+
"ready": "選取資料夾並輸入搜尋樣式",
|
|
32
|
+
"searching": "正在搜尋 {current}/{total}:{name}",
|
|
33
|
+
"done": "完成:搜尋 {scanned} 份 PDF,{files} 份有結果,共 {matches} 筆命中",
|
|
34
|
+
"cancelled": "搜尋已取消:已處理 {scanned} 份 PDF",
|
|
35
|
+
"no_results": "找不到符合項目",
|
|
36
|
+
"choose_folder": "選取 PDF 資料夾",
|
|
37
|
+
"invalid": "無法搜尋",
|
|
38
|
+
"warnings": "部分檔案無法搜尋",
|
|
39
|
+
"warning_body": "略過 {count} 份檔案:\n\n{details}",
|
|
40
|
+
"language": "語言",
|
|
41
|
+
"theme": "主題",
|
|
42
|
+
"system": "跟隨系統",
|
|
43
|
+
"light": "淺色",
|
|
44
|
+
"dark": "深色",
|
|
45
|
+
"traditional_chinese": "繁體中文",
|
|
46
|
+
"english": "English",
|
|
47
|
+
"page": "第 {page} 頁,共 {total} 頁",
|
|
48
|
+
"previous_page": "上一個命中頁",
|
|
49
|
+
"next_page": "下一個命中頁",
|
|
50
|
+
"zoom_out": "縮小",
|
|
51
|
+
"zoom_in": "放大",
|
|
52
|
+
"fit": "符合寬度",
|
|
53
|
+
"page_single": "第 {start} 頁",
|
|
54
|
+
"page_range": "第 {start}–{end} 頁",
|
|
55
|
+
"match_count": "{count} 筆",
|
|
56
|
+
},
|
|
57
|
+
"en": {
|
|
58
|
+
"app_title": "rgpdf — PDF Search",
|
|
59
|
+
"folder": "PDF folder",
|
|
60
|
+
"browse": "Browse…",
|
|
61
|
+
"pattern": "Search pattern",
|
|
62
|
+
"pattern_hint": "Enter text or a regular expression",
|
|
63
|
+
"plain": "Plain text",
|
|
64
|
+
"regex": "Regular Expression",
|
|
65
|
+
"case": "Case sensitive",
|
|
66
|
+
"recursive": "Include subfolders",
|
|
67
|
+
"highlight_settings": "Highlight color",
|
|
68
|
+
"common_colors": "Common colors",
|
|
69
|
+
"custom_color": "Custom color…",
|
|
70
|
+
"choose_highlight_color": "Choose highlight color",
|
|
71
|
+
"settings": "Settings",
|
|
72
|
+
"enabled": "On",
|
|
73
|
+
"disabled": "Off",
|
|
74
|
+
"search": "Search",
|
|
75
|
+
"cancel": "Cancel",
|
|
76
|
+
"files": "Files",
|
|
77
|
+
"matches": "Matches",
|
|
78
|
+
"filename": "File name",
|
|
79
|
+
"count": "Count",
|
|
80
|
+
"page_number": "Page",
|
|
81
|
+
"paragraph": "Paragraph",
|
|
82
|
+
"preview": "Page preview",
|
|
83
|
+
"ready": "Choose a folder and enter a search pattern",
|
|
84
|
+
"searching": "Searching {current}/{total}: {name}",
|
|
85
|
+
"done": "Done: searched {scanned} PDFs, {files} files matched, {matches} matches",
|
|
86
|
+
"cancelled": "Search cancelled after {scanned} PDFs",
|
|
87
|
+
"no_results": "No matches found",
|
|
88
|
+
"choose_folder": "Choose PDF folder",
|
|
89
|
+
"invalid": "Cannot search",
|
|
90
|
+
"warnings": "Some files could not be searched",
|
|
91
|
+
"warning_body": "Skipped {count} files:\n\n{details}",
|
|
92
|
+
"language": "Language",
|
|
93
|
+
"theme": "Theme",
|
|
94
|
+
"system": "Follow system",
|
|
95
|
+
"light": "Light",
|
|
96
|
+
"dark": "Dark",
|
|
97
|
+
"traditional_chinese": "繁體中文",
|
|
98
|
+
"english": "English",
|
|
99
|
+
"page": "Page {page} of {total}",
|
|
100
|
+
"previous_page": "Previous matched page",
|
|
101
|
+
"next_page": "Next matched page",
|
|
102
|
+
"zoom_out": "Zoom out",
|
|
103
|
+
"zoom_in": "Zoom in",
|
|
104
|
+
"fit": "Fit width",
|
|
105
|
+
"page_single": "Page {start}",
|
|
106
|
+
"page_range": "Pages {start}–{end}",
|
|
107
|
+
"match_count": "{count} matches",
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def translate(language: str, key: str, **values: object) -> str:
|
|
113
|
+
catalog = TRANSLATIONS.get(language, TRANSLATIONS["en"])
|
|
114
|
+
return catalog.get(key, TRANSLATIONS["en"].get(key, key)).format(**values)
|