debconf-translator 0.4.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.
- debconf_translator-0.4.1/PKG-INFO +100 -0
- debconf_translator-0.4.1/README.md +89 -0
- debconf_translator-0.4.1/debconf_translator/__init__.py +20 -0
- debconf_translator-0.4.1/debconf_translator/__main__.py +6 -0
- debconf_translator-0.4.1/debconf_translator/app.py +178 -0
- debconf_translator-0.4.1/debconf_translator/cli/__init__.py +125 -0
- debconf_translator-0.4.1/debconf_translator/models.py +77 -0
- debconf_translator-0.4.1/debconf_translator/scraper.py +259 -0
- debconf_translator-0.4.1/debconf_translator/views/__init__.py +1 -0
- debconf_translator-0.4.1/debconf_translator/views/dashboard.py +210 -0
- debconf_translator-0.4.1/debconf_translator/views/packages.py +273 -0
- debconf_translator-0.4.1/debconf_translator/views/reviews.py +226 -0
- debconf_translator-0.4.1/debconf_translator/views/settings.py +194 -0
- debconf_translator-0.4.1/debconf_translator/views/submit.py +409 -0
- debconf_translator-0.4.1/debconf_translator/widgets/__init__.py +1 -0
- debconf_translator-0.4.1/debconf_translator/widgets/po_editor.py +407 -0
- debconf_translator-0.4.1/debconf_translator/window.py +262 -0
- debconf_translator-0.4.1/debconf_translator.egg-info/PKG-INFO +100 -0
- debconf_translator-0.4.1/debconf_translator.egg-info/SOURCES.txt +24 -0
- debconf_translator-0.4.1/debconf_translator.egg-info/dependency_links.txt +1 -0
- debconf_translator-0.4.1/debconf_translator.egg-info/entry_points.txt +2 -0
- debconf_translator-0.4.1/debconf_translator.egg-info/top_level.txt +1 -0
- debconf_translator-0.4.1/pyproject.toml +22 -0
- debconf_translator-0.4.1/setup.cfg +4 -0
- debconf_translator-0.4.1/setup.py +12 -0
- debconf_translator-0.4.1/tests/test_release_sanity.py +25 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: debconf-translator
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: GTK4/Adwaita tool for managing Debconf template translations
|
|
5
|
+
Author-email: Daniel Nylander <daniel@danielnylander.se>
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/yeager/debconf-translator
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/yeager/debconf-translator/issues
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Debconf Translation Manager
|
|
13
|
+
|
|
14
|
+
A GTK4/Adwaita tool for managing Debconf template translations in Debian and Ubuntu.
|
|
15
|
+
|
|
16
|
+
<img width="1206" height="754" alt="image" src="https://github.com/user-attachments/assets/801eb597-ecab-428d-a843-f3fc85bb7e00" />
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Dashboard** — Overview of translation status per language with statistics
|
|
22
|
+
- **Package Browser** — Browse untranslated packages sorted by popularity (popcon)
|
|
23
|
+
- **Built-in PO Editor** — Edit translations inline with source/translation side-by-side
|
|
24
|
+
- **Review Board** — Track debconf templates under review on l10n.debian.org
|
|
25
|
+
- **BTS Submission** — Generate bug reports for submitting translations
|
|
26
|
+
- **CLI** — Command-line interface for status checks and batch operations
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
### PyPI / pipx (Debian/Ubuntu)
|
|
31
|
+
|
|
32
|
+
Install GTK4, libadwaita and the system PyGObject bindings first; pip cannot
|
|
33
|
+
install these native system libraries:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
sudo apt install python3-gi gir1.2-gtk-4.0 gir1.2-adw-1 pipx
|
|
37
|
+
pipx install --system-site-packages debconf-translator
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### From .deb package (Debian/Ubuntu)
|
|
42
|
+
```bash
|
|
43
|
+
# Add the repository
|
|
44
|
+
echo "deb https://yeager.github.io/debian-repo/ stable main" | sudo tee /etc/apt/sources.list.d/yeager.list
|
|
45
|
+
sudo apt update && sudo apt install debconf-translator
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### From .rpm package (Fedora)
|
|
49
|
+
```bash
|
|
50
|
+
sudo dnf install debconf-translator
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### From source
|
|
54
|
+
```bash
|
|
55
|
+
pip install .
|
|
56
|
+
debconf-translator
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
### GUI
|
|
62
|
+
```bash
|
|
63
|
+
debconf-translator
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### CLI
|
|
67
|
+
```bash
|
|
68
|
+
# Check translation status
|
|
69
|
+
debconf-translator status sv
|
|
70
|
+
debconf-translator status sv --json
|
|
71
|
+
|
|
72
|
+
# Download untranslated templates
|
|
73
|
+
debconf-translator fetch sv -o ./translations/
|
|
74
|
+
|
|
75
|
+
# Show packages under review
|
|
76
|
+
debconf-translator reviews
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Keyboard Shortcuts
|
|
80
|
+
|
|
81
|
+
| Shortcut | Action |
|
|
82
|
+
|----------|--------|
|
|
83
|
+
| Ctrl+1 | Dashboard |
|
|
84
|
+
| Ctrl+2 | Packages |
|
|
85
|
+
| Ctrl+3 | Review Board |
|
|
86
|
+
| Ctrl+4 | Submit |
|
|
87
|
+
| Ctrl+T | Toggle Dark Theme |
|
|
88
|
+
| Ctrl+Q | Quit |
|
|
89
|
+
|
|
90
|
+
## Translation
|
|
91
|
+
|
|
92
|
+
Translations are managed on [Transifex](https://www.transifex.com/danielnylander/debconf-translator/).
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
GPL-3.0-or-later
|
|
97
|
+
|
|
98
|
+
## Author
|
|
99
|
+
|
|
100
|
+
Daniel Nylander <daniel@danielnylander.se>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Debconf Translation Manager
|
|
2
|
+
|
|
3
|
+
A GTK4/Adwaita tool for managing Debconf template translations in Debian and Ubuntu.
|
|
4
|
+
|
|
5
|
+
<img width="1206" height="754" alt="image" src="https://github.com/user-attachments/assets/801eb597-ecab-428d-a843-f3fc85bb7e00" />
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Dashboard** — Overview of translation status per language with statistics
|
|
11
|
+
- **Package Browser** — Browse untranslated packages sorted by popularity (popcon)
|
|
12
|
+
- **Built-in PO Editor** — Edit translations inline with source/translation side-by-side
|
|
13
|
+
- **Review Board** — Track debconf templates under review on l10n.debian.org
|
|
14
|
+
- **BTS Submission** — Generate bug reports for submitting translations
|
|
15
|
+
- **CLI** — Command-line interface for status checks and batch operations
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### PyPI / pipx (Debian/Ubuntu)
|
|
20
|
+
|
|
21
|
+
Install GTK4, libadwaita and the system PyGObject bindings first; pip cannot
|
|
22
|
+
install these native system libraries:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
sudo apt install python3-gi gir1.2-gtk-4.0 gir1.2-adw-1 pipx
|
|
26
|
+
pipx install --system-site-packages debconf-translator
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### From .deb package (Debian/Ubuntu)
|
|
31
|
+
```bash
|
|
32
|
+
# Add the repository
|
|
33
|
+
echo "deb https://yeager.github.io/debian-repo/ stable main" | sudo tee /etc/apt/sources.list.d/yeager.list
|
|
34
|
+
sudo apt update && sudo apt install debconf-translator
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### From .rpm package (Fedora)
|
|
38
|
+
```bash
|
|
39
|
+
sudo dnf install debconf-translator
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### From source
|
|
43
|
+
```bash
|
|
44
|
+
pip install .
|
|
45
|
+
debconf-translator
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### GUI
|
|
51
|
+
```bash
|
|
52
|
+
debconf-translator
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### CLI
|
|
56
|
+
```bash
|
|
57
|
+
# Check translation status
|
|
58
|
+
debconf-translator status sv
|
|
59
|
+
debconf-translator status sv --json
|
|
60
|
+
|
|
61
|
+
# Download untranslated templates
|
|
62
|
+
debconf-translator fetch sv -o ./translations/
|
|
63
|
+
|
|
64
|
+
# Show packages under review
|
|
65
|
+
debconf-translator reviews
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Keyboard Shortcuts
|
|
69
|
+
|
|
70
|
+
| Shortcut | Action |
|
|
71
|
+
|----------|--------|
|
|
72
|
+
| Ctrl+1 | Dashboard |
|
|
73
|
+
| Ctrl+2 | Packages |
|
|
74
|
+
| Ctrl+3 | Review Board |
|
|
75
|
+
| Ctrl+4 | Submit |
|
|
76
|
+
| Ctrl+T | Toggle Dark Theme |
|
|
77
|
+
| Ctrl+Q | Quit |
|
|
78
|
+
|
|
79
|
+
## Translation
|
|
80
|
+
|
|
81
|
+
Translations are managed on [Transifex](https://www.transifex.com/danielnylander/debconf-translator/).
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
GPL-3.0-or-later
|
|
86
|
+
|
|
87
|
+
## Author
|
|
88
|
+
|
|
89
|
+
Daniel Nylander <daniel@danielnylander.se>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Debconf Translation Manager — GTK4/Adwaita tool for managing Debconf template translations."""
|
|
2
|
+
|
|
3
|
+
import gettext
|
|
4
|
+
import locale
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
__version__ = "0.4.1"
|
|
8
|
+
__app_id__ = "se.danielnylander.debconf-translator"
|
|
9
|
+
__author__ = "Daniel Nylander"
|
|
10
|
+
__email__ = "daniel@danielnylander.se"
|
|
11
|
+
|
|
12
|
+
# Centralized locale/gettext setup — all modules import _ from here
|
|
13
|
+
_localedir = os.path.join(os.path.dirname(__file__), '..', 'locale')
|
|
14
|
+
if not os.path.isdir(_localedir):
|
|
15
|
+
_localedir = '/usr/share/locale'
|
|
16
|
+
|
|
17
|
+
gettext.bindtextdomain('debconf-translator', _localedir)
|
|
18
|
+
gettext.textdomain('debconf-translator')
|
|
19
|
+
|
|
20
|
+
_ = gettext.gettext
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""GTK4/Adwaita application entry point."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import gi
|
|
10
|
+
gi.require_version("Gtk", "4.0")
|
|
11
|
+
gi.require_version("Adw", "1")
|
|
12
|
+
from gi.repository import Adw, Gio, GLib, Gtk
|
|
13
|
+
|
|
14
|
+
from . import __app_id__, __version__, _
|
|
15
|
+
|
|
16
|
+
log = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
CONFIG_DIR = Path(GLib.get_user_config_dir()) / "debconf-translator"
|
|
19
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load_config() -> dict:
|
|
23
|
+
if CONFIG_FILE.exists():
|
|
24
|
+
try:
|
|
25
|
+
return json.loads(CONFIG_FILE.read_text())
|
|
26
|
+
except (json.JSONDecodeError, OSError):
|
|
27
|
+
pass
|
|
28
|
+
return {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def save_config(config: dict) -> None:
|
|
32
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
CONFIG_FILE.write_text(json.dumps(config, indent=2))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DebconfTranslatorApp(Adw.Application):
|
|
37
|
+
def __init__(self):
|
|
38
|
+
super().__init__(
|
|
39
|
+
application_id=__app_id__,
|
|
40
|
+
flags=Gio.ApplicationFlags.DEFAULT_FLAGS,
|
|
41
|
+
)
|
|
42
|
+
self._setup_actions()
|
|
43
|
+
|
|
44
|
+
def _setup_actions(self):
|
|
45
|
+
actions = [
|
|
46
|
+
("quit", self._on_quit, ["<primary>q"]),
|
|
47
|
+
("about", self._on_about, None),
|
|
48
|
+
("shortcuts", self._on_shortcuts, ["<primary>question"]),
|
|
49
|
+
("theme", self._on_toggle_theme, ["<primary>t"]),
|
|
50
|
+
]
|
|
51
|
+
for name, callback, accels in actions:
|
|
52
|
+
action = Gio.SimpleAction.new(name, None)
|
|
53
|
+
action.connect("activate", callback)
|
|
54
|
+
self.add_action(action)
|
|
55
|
+
if accels:
|
|
56
|
+
self.set_accels_for_action(f"app.{name}", accels)
|
|
57
|
+
|
|
58
|
+
self.set_accels_for_action("win.navigate-dashboard", ["<primary>1"])
|
|
59
|
+
self.set_accels_for_action("win.navigate-packages", ["<primary>2"])
|
|
60
|
+
self.set_accels_for_action("win.navigate-reviews", ["<primary>3"])
|
|
61
|
+
self.set_accels_for_action("win.navigate-submit", ["<primary>4"])
|
|
62
|
+
|
|
63
|
+
def do_activate(self):
|
|
64
|
+
from .window import DebconfTranslatorWindow
|
|
65
|
+
|
|
66
|
+
win = self.props.active_window
|
|
67
|
+
if not win:
|
|
68
|
+
win = DebconfTranslatorWindow(application=self)
|
|
69
|
+
win.present()
|
|
70
|
+
|
|
71
|
+
def _on_quit(self, *_args):
|
|
72
|
+
self.quit()
|
|
73
|
+
|
|
74
|
+
def _on_about(self, *_args):
|
|
75
|
+
win = self.props.active_window
|
|
76
|
+
about = Adw.AboutDialog.new()
|
|
77
|
+
about.set_application_name(_("Debconf Translation Manager"))
|
|
78
|
+
about.set_application_icon(__app_id__)
|
|
79
|
+
about.set_version(__version__)
|
|
80
|
+
about.set_developer_name("Daniel Nylander")
|
|
81
|
+
about.set_license_type(Gtk.License.GPL_3_0)
|
|
82
|
+
about.set_website("https://github.com/yeager/debconf-translator")
|
|
83
|
+
about.set_issue_url("https://github.com/yeager/debconf-translator/issues")
|
|
84
|
+
about.set_developers(["Daniel Nylander <daniel@danielnylander.se>"])
|
|
85
|
+
about.set_copyright("© 2026 Daniel Nylander")
|
|
86
|
+
about.set_comments(
|
|
87
|
+
_("GTK4/Adwaita tool for managing Debconf template translations")
|
|
88
|
+
)
|
|
89
|
+
debug_lines = [
|
|
90
|
+
f"debconf-translator {__version__}",
|
|
91
|
+
f"GTK {Gtk.get_major_version()}.{Gtk.get_minor_version()}.{Gtk.get_micro_version()}",
|
|
92
|
+
f"libadwaita {Adw.get_major_version()}.{Adw.get_minor_version()}.{Adw.get_micro_version()}",
|
|
93
|
+
f"Python {sys.version}",
|
|
94
|
+
f"OS: {os.uname().sysname} {os.uname().release}",
|
|
95
|
+
]
|
|
96
|
+
about.set_debug_info("\n".join(debug_lines))
|
|
97
|
+
about.set_debug_info_filename("debconf-translator-debug.txt")
|
|
98
|
+
about.present(win)
|
|
99
|
+
|
|
100
|
+
def _on_shortcuts(self, *_args):
|
|
101
|
+
win = self.props.active_window
|
|
102
|
+
builder = Gtk.Builder.new_from_string(SHORTCUTS_UI, -1)
|
|
103
|
+
shortcuts_win = builder.get_object("shortcuts")
|
|
104
|
+
shortcuts_win.set_transient_for(win)
|
|
105
|
+
shortcuts_win.present()
|
|
106
|
+
|
|
107
|
+
def _on_toggle_theme(self, *_args):
|
|
108
|
+
sm = Adw.StyleManager.get_default()
|
|
109
|
+
if sm.get_dark():
|
|
110
|
+
sm.set_color_scheme(Adw.ColorScheme.FORCE_LIGHT)
|
|
111
|
+
else:
|
|
112
|
+
sm.set_color_scheme(Adw.ColorScheme.FORCE_DARK)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
SHORTCUTS_UI = """\
|
|
116
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
117
|
+
<interface>
|
|
118
|
+
<object class="GtkShortcutsWindow" id="shortcuts">
|
|
119
|
+
<property name="modal">1</property>
|
|
120
|
+
<child>
|
|
121
|
+
<object class="GtkShortcutsSection">
|
|
122
|
+
<property name="section-name">shortcuts</property>
|
|
123
|
+
<child>
|
|
124
|
+
<object class="GtkShortcutsGroup">
|
|
125
|
+
<property name="title" translatable="yes">General</property>
|
|
126
|
+
<child>
|
|
127
|
+
<object class="GtkShortcutsShortcut">
|
|
128
|
+
<property name="title" translatable="yes">Toggle Dark Theme</property>
|
|
129
|
+
<property name="accelerator"><primary>t</property>
|
|
130
|
+
</object>
|
|
131
|
+
</child>
|
|
132
|
+
<child>
|
|
133
|
+
<object class="GtkShortcutsShortcut">
|
|
134
|
+
<property name="title" translatable="yes">Quit</property>
|
|
135
|
+
<property name="accelerator"><primary>q</property>
|
|
136
|
+
</object>
|
|
137
|
+
</child>
|
|
138
|
+
</object>
|
|
139
|
+
</child>
|
|
140
|
+
<child>
|
|
141
|
+
<object class="GtkShortcutsGroup">
|
|
142
|
+
<property name="title" translatable="yes">Navigation</property>
|
|
143
|
+
<child>
|
|
144
|
+
<object class="GtkShortcutsShortcut">
|
|
145
|
+
<property name="title" translatable="yes">Dashboard</property>
|
|
146
|
+
<property name="accelerator"><primary>1</property>
|
|
147
|
+
</object>
|
|
148
|
+
</child>
|
|
149
|
+
<child>
|
|
150
|
+
<object class="GtkShortcutsShortcut">
|
|
151
|
+
<property name="title" translatable="yes">Packages</property>
|
|
152
|
+
<property name="accelerator"><primary>2</property>
|
|
153
|
+
</object>
|
|
154
|
+
</child>
|
|
155
|
+
<child>
|
|
156
|
+
<object class="GtkShortcutsShortcut">
|
|
157
|
+
<property name="title" translatable="yes">Review Board</property>
|
|
158
|
+
<property name="accelerator"><primary>3</property>
|
|
159
|
+
</object>
|
|
160
|
+
</child>
|
|
161
|
+
<child>
|
|
162
|
+
<object class="GtkShortcutsShortcut">
|
|
163
|
+
<property name="title" translatable="yes">Submit</property>
|
|
164
|
+
<property name="accelerator"><primary>4</property>
|
|
165
|
+
</object>
|
|
166
|
+
</child>
|
|
167
|
+
</object>
|
|
168
|
+
</child>
|
|
169
|
+
</object>
|
|
170
|
+
</child>
|
|
171
|
+
</object>
|
|
172
|
+
</interface>
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def main():
|
|
177
|
+
app = DebconfTranslatorApp()
|
|
178
|
+
return app.run(sys.argv)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""CLI interface for debconf-translator."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from .. import __version__, _
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
parser = argparse.ArgumentParser(
|
|
12
|
+
prog="debconf-translator",
|
|
13
|
+
description=_("Debconf Translation Manager — manage debconf template translations"),
|
|
14
|
+
)
|
|
15
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
16
|
+
|
|
17
|
+
sub = parser.add_subparsers(dest="command")
|
|
18
|
+
|
|
19
|
+
# GUI
|
|
20
|
+
sub.add_parser("gui", help=_("Launch the graphical interface"))
|
|
21
|
+
|
|
22
|
+
# Status
|
|
23
|
+
status_p = sub.add_parser("status", help=_("Show translation status for a language"))
|
|
24
|
+
status_p.add_argument("lang", default="sv", nargs="?", help=_("Language code (default: sv)"))
|
|
25
|
+
status_p.add_argument("--json", "-j", action="store_true", help=_("Output as JSON"))
|
|
26
|
+
status_p.add_argument("-q", "--quiet", action="store_true", help=_("Minimal output"))
|
|
27
|
+
|
|
28
|
+
# Fetch
|
|
29
|
+
fetch_p = sub.add_parser("fetch", help=_("Download POT files for untranslated packages"))
|
|
30
|
+
fetch_p.add_argument("lang", default="sv", nargs="?")
|
|
31
|
+
fetch_p.add_argument("--output", "-o", default=".", help=_("Output directory"))
|
|
32
|
+
|
|
33
|
+
# Reviews
|
|
34
|
+
reviews_p = sub.add_parser("reviews", help=_("Show packages under review"))
|
|
35
|
+
reviews_p.add_argument("--json", "-j", action="store_true", help=_("Output as JSON"))
|
|
36
|
+
|
|
37
|
+
args = parser.parse_args()
|
|
38
|
+
|
|
39
|
+
if args.command is None or args.command == "gui":
|
|
40
|
+
from ..app import main as gui_main
|
|
41
|
+
return gui_main()
|
|
42
|
+
|
|
43
|
+
if args.command == "status":
|
|
44
|
+
return _cmd_status(args)
|
|
45
|
+
elif args.command == "fetch":
|
|
46
|
+
return _cmd_fetch(args)
|
|
47
|
+
elif args.command == "reviews":
|
|
48
|
+
return _cmd_reviews(args)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _cmd_status(args):
|
|
52
|
+
from ..scraper import fetch_language_status
|
|
53
|
+
stats, packages, translators = fetch_language_status(args.lang)
|
|
54
|
+
|
|
55
|
+
if args.json:
|
|
56
|
+
import json
|
|
57
|
+
print(json.dumps({
|
|
58
|
+
"language": stats.code,
|
|
59
|
+
"translated": stats.translated,
|
|
60
|
+
"total": stats.total,
|
|
61
|
+
"progress": round(stats.progress, 1),
|
|
62
|
+
"untranslated_packages": len(packages),
|
|
63
|
+
"packages": [{"name": p.name, "strings": p.strings_total} for p in packages],
|
|
64
|
+
}, indent=2))
|
|
65
|
+
elif args.quiet:
|
|
66
|
+
print(f"{stats.translated}/{stats.total} ({stats.progress:.1f}%)")
|
|
67
|
+
else:
|
|
68
|
+
print(f"Language: {args.lang}")
|
|
69
|
+
print(f"Translated: {stats.translated}/{stats.total} ({stats.progress:.1f}%)")
|
|
70
|
+
print(f"Untranslated packages: {len(packages)}")
|
|
71
|
+
if packages:
|
|
72
|
+
print()
|
|
73
|
+
packages.sort(key=lambda p: p.strings_total, reverse=True)
|
|
74
|
+
for p in packages[:20]:
|
|
75
|
+
print(f" {p.name:40s} {p.strings_total:4d} strings")
|
|
76
|
+
if len(packages) > 20:
|
|
77
|
+
print(f" ... and {len(packages) - 20} more")
|
|
78
|
+
|
|
79
|
+
if translators:
|
|
80
|
+
medals = ["🥇", "🥈", "🥉"]
|
|
81
|
+
print(f"\nTop translators ({args.lang}):")
|
|
82
|
+
for i, t in enumerate(translators[:10]):
|
|
83
|
+
medal = medals[i] if i < 3 else f" #{i+1}"
|
|
84
|
+
print(f" {medal} {t.name:35s} {t.strings:5d} strings ({t.packages} pkgs)")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _cmd_fetch(args):
|
|
88
|
+
from ..scraper import fetch_language_status, fetch_pot_file
|
|
89
|
+
from pathlib import Path
|
|
90
|
+
|
|
91
|
+
outdir = Path(args.output)
|
|
92
|
+
outdir.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
|
|
94
|
+
_, packages, _ = fetch_language_status(args.lang)
|
|
95
|
+
print(f"Found {len(packages)} untranslated packages")
|
|
96
|
+
|
|
97
|
+
for pkg in packages:
|
|
98
|
+
pot = fetch_pot_file(pkg.name)
|
|
99
|
+
if pot:
|
|
100
|
+
(outdir / f"{pkg.name}.pot").write_text(pot)
|
|
101
|
+
print(f" ✓ {pkg.name}")
|
|
102
|
+
else:
|
|
103
|
+
print(f" ✗ {pkg.name} (not found)")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _cmd_reviews(args):
|
|
107
|
+
from ..scraper import fetch_reviews
|
|
108
|
+
reviews = fetch_reviews()
|
|
109
|
+
|
|
110
|
+
if not reviews:
|
|
111
|
+
print("No active reviews found")
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
if args.json:
|
|
115
|
+
import json
|
|
116
|
+
print(json.dumps([{
|
|
117
|
+
"package": r.package,
|
|
118
|
+
"status": r.status,
|
|
119
|
+
"bug": r.bug_number,
|
|
120
|
+
} for r in reviews], indent=2))
|
|
121
|
+
else:
|
|
122
|
+
print(f"{len(reviews)} packages under review:")
|
|
123
|
+
for r in reviews:
|
|
124
|
+
bug = f" (#{r.bug_number})" if r.bug_number else ""
|
|
125
|
+
print(f" {r.package:40s} {r.status:10s}{bug}")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from . import _
|
|
2
|
+
"""Data models for debconf translation tracking."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from dataclasses import dataclass, field, asdict
|
|
6
|
+
from enum import IntEnum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TranslationStatus(IntEnum):
|
|
12
|
+
UNTRANSLATED = 0
|
|
13
|
+
IN_PROGRESS = 1
|
|
14
|
+
TRANSLATED = 2
|
|
15
|
+
SUBMITTED = 3 # Sent to BTS
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class DebconfPackage:
|
|
20
|
+
"""A Debian package with debconf templates."""
|
|
21
|
+
name: str
|
|
22
|
+
section: str = "main"
|
|
23
|
+
strings_total: int = 0
|
|
24
|
+
strings_translated: int = 0
|
|
25
|
+
popcon_rank: int = 0
|
|
26
|
+
status: TranslationStatus = TranslationStatus.UNTRANSLATED
|
|
27
|
+
pot_url: str = ""
|
|
28
|
+
po_path: str = "" # Local path to .po file
|
|
29
|
+
bts_bug: str = "" # Bug number if submitted
|
|
30
|
+
notes: str = ""
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def progress(self) -> float:
|
|
34
|
+
if self.strings_total == 0:
|
|
35
|
+
return 0.0
|
|
36
|
+
return self.strings_translated / self.strings_total * 100
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def display_status(self) -> str:
|
|
40
|
+
return {
|
|
41
|
+
TranslationStatus.UNTRANSLATED: _("Untranslated"),
|
|
42
|
+
TranslationStatus.IN_PROGRESS: _("In Progress"),
|
|
43
|
+
TranslationStatus.TRANSLATED: _("Translated"),
|
|
44
|
+
TranslationStatus.SUBMITTED: _("Submitted"),
|
|
45
|
+
}.get(self.status, "?")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class ReviewItem:
|
|
50
|
+
"""A debconf template under review on l10n.debian.org."""
|
|
51
|
+
package: str
|
|
52
|
+
status: str = "" # "bts", "review", etc.
|
|
53
|
+
bug_number: str = ""
|
|
54
|
+
reviewer: str = ""
|
|
55
|
+
date: str = ""
|
|
56
|
+
original_strings: list = field(default_factory=list)
|
|
57
|
+
new_strings: list = field(default_factory=list)
|
|
58
|
+
url: str = ""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class LanguageStats:
|
|
63
|
+
"""Statistics for a language."""
|
|
64
|
+
code: str
|
|
65
|
+
name: str
|
|
66
|
+
translated: int = 0
|
|
67
|
+
total: int = 0
|
|
68
|
+
untranslated_packages: int = 0
|
|
69
|
+
in_progress_packages: int = 0
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def progress(self) -> float:
|
|
73
|
+
if self.total == 0:
|
|
74
|
+
return 0.0
|
|
75
|
+
return self.translated / self.total * 100
|
|
76
|
+
|
|
77
|
+
|