Skip to content

Commit 9afdfc5

Browse files
committed
3.15: solved link issues
1 parent a095dbb commit 9afdfc5

3 files changed

Lines changed: 166 additions & 36 deletions

File tree

library/functions.po

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2129,12 +2129,12 @@ msgid ""
21292129
"``-`` (with no space in between), have leading zeros, be surrounded by "
21302130
"whitespace, and have single underscores interspersed between digits."
21312131
msgstr ""
2132-
"Wenn das Argument keine Zahl ist oder wenn *base* angegeben wird, muss es "
2133-
"sich um eine :class:`str`, :class:`bytes` oder :class:`bytearray` Instanz "
2134-
"handeln, die eine Ganzzahl zur Basis *base* darstellt. Optional kann der "
2132+
"Ist das Argument keine Zahl oder wird *base* angegeben, muss es eine "
2133+
"Zeichenkette oder eine Instanz von :class:`bytes` oder :class:`bytearray` "
2134+
"sein, die eine Ganzzahl zur Basis *base* darstellt. Optional kann der "
21352135
"Zeichenkette ein ``+`` oder ``-`` vorangestellt sein (ohne Leerzeichen "
2136-
"dazwischen); zudem kann sie führende Nullen aufweisen, von Leerzeichen "
2137-
"umgeben sein und einzelne Unterstriche zwischen den Ziffern enthalten."
2136+
"dazwischen), sie kann führende Nullen haben, von Whitespace umgeben sein "
2137+
"und einzelne Unterstriche zwischen den Ziffern eingestreut haben."
21382138

21392139
#: ../../library/functions.rst:1074
21402140
msgid ""
@@ -3151,15 +3151,16 @@ msgstr ""
31513151
"Operandentypen handelt, gelten die Regeln zur Typanpassung für binäre "
31523152
"arithmetische Operatoren. Bei Operanden vom Typ :class:`int` hat das "
31533153
"Ergebnis denselben Typ wie die Operanden (nach der Typanpassung), es sei "
3154-
"denn, das zweite Argument ist negativ; in diesem Fall werden alle Argumente "
3155-
"in :class:`float` umgewandelt und das Ergebnis ist vom Typ :class:`float`. "
3156-
"So liefert beispielsweise ``pow(10, 2)`` den Wert ``100``, während ``pow(10, "
3154+
"denn, das zweite Argument ist negativ; in diesem Fall werden alle "
3155+
"Argumente in float umgewandelt und ein float-Ergebnis geliefert. So "
3156+
"liefert beispielsweise ``pow(10, 2)`` den Wert ``100``, während ``pow(10, "
31573157
"-2)`` den Wert ``0.01`` ergibt. Bei einer negativen Basis vom Typ :class:"
3158-
"`int` oder :class:`float` und einem nicht ganzzahligen Exponenten wird ein "
3159-
"komplexes Ergebnis geliefert. Zum Beispiel liefert ``pow(-9, 0.5)`` einen "
3160-
"Wert nahe ``3j``. Ist die Basis hingegen negativ (vom Typ :class:`int` oder :"
3161-
"class:`float`) und der Exponent ganzzahlig, so wird ein Ergebnis vom Typ :"
3162-
"class:`float` geliefert. So ergibt ``pow(-9, 2.0)`` beispielsweise ``81.0``."
3158+
"`int` oder :class:`float` und einem nicht ganzzahligen Exponenten wird "
3159+
"ein komplexes Ergebnis geliefert. Zum Beispiel liefert ``pow(-9, 0.5)`` "
3160+
"einen Wert nahe ``3j``. Ist die Basis hingegen negativ (vom Typ :class:"
3161+
"`int` oder :class:`float`) und der Exponent ganzzahlig, so wird ein "
3162+
"float-Ergebnis geliefert. So ergibt ``pow(-9, 2.0)`` beispielsweise "
3163+
"``81.0``."
31633164

31643165
#: ../../library/functions.rst:1609
31653166
msgid ""
@@ -4277,15 +4278,16 @@ msgid ""
42774278
"__dict__` attribute. The following two statements create identical :class:`!"
42784279
"type` objects:"
42794280
msgstr ""
4280-
"Gibt bei Angabe von drei Argumenten ein neues Typobjekt zurück. Dies ist im "
4281-
"Wesentlichen eine dynamische Form der :keyword:`class`-Anweisung. Die "
4282-
"Zeichenkette *name* ist der Klassenname und wird zum Attribut :attr:`~type."
4283-
"__name__`. Das Tupel *bases* enthält die Basisklassen und wird zum Attribut :"
4284-
"attr:`~type.__bases__`; ist es leer, wird :class:`object` – die gemeinsame "
4285-
"Basisklasse aller Klassen – hinzugefügt. Das Dictionary *dict* enthält "
4286-
"Attribut- und Methodendefinitionen für den Klassenkörper; es kann kopiert "
4287-
"oder gekapselt werden, bevor es zum Attribut :attr:`~type.__dict__` wird. "
4288-
"Die folgenden beiden Anweisungen erzeugen identische :class:`type`-Objekte:"
4281+
"Gibt bei Angabe von drei Argumenten ein neues Typobjekt zurück. Dies ist "
4282+
"im Wesentlichen eine dynamische Form der :keyword:`class`-Anweisung. Die "
4283+
"Zeichenkette *name* ist der Klassenname und wird zum Attribut :attr:"
4284+
"`~type.__name__`. Das Tupel *bases* enthält die Basisklassen und wird "
4285+
"zum Attribut :attr:`~type.__bases__`; ist es leer, wird :class:`object` "
4286+
"– die gemeinsame Basisklasse aller Klassen – hinzugefügt. Das Dictionary "
4287+
"*dict* enthält Attribut- und Methodendefinitionen für den Klassenkörper; "
4288+
"es kann kopiert oder gekapselt werden, bevor es zum Attribut :attr:"
4289+
"`~type.__dict__` wird. Die folgenden beiden Anweisungen erzeugen "
4290+
"identische :class:`!type`-Objekte:"
42894291

42904292
#: ../../library/functions.rst:2172
42914293
msgid "See also:"
@@ -4600,14 +4602,15 @@ msgid ""
46004602
"implementation is in use. Direct use of :func:`__import__` is also "
46014603
"discouraged in favor of :func:`importlib.import_module`."
46024604
msgstr ""
4603-
"Diese Funktion wird durch die :keyword:`import`-Anweisung aufgerufen. Sie "
4604-
"kann ersetzt werden (durch Importieren des Moduls :mod:`builtins` und "
4605-
"Zuweisung an ``builtins.__import__``), um die Semantik der :keyword:`import`-"
4606-
"Anweisung zu ändern; davon wird jedoch **dringend** abgeraten. Es ist meist "
4607-
"einfacher, Import-Hooks (siehe :pep:`302`) zu verwenden, um dieselben Ziele "
4608-
"zu erreichen, ohne Probleme bei Code zu verursachen, der von der Standard-"
4609-
"Importimplementierung ausgeht. Auch von der direkten Verwendung von :func:"
4610-
"`__import__` wird zugunsten von :func:`importlib.import_module` abgeraten."
4605+
"Diese Funktion wird durch die :keyword:`import`-Anweisung aufgerufen. "
4606+
"Sie kann ersetzt werden (durch Importieren des Moduls :mod:`builtins` "
4607+
"und Zuweisung an ``builtins.__import__``), um die Semantik der :keyword:"
4608+
"`!import`-Anweisung zu ändern; davon wird jedoch **dringend** "
4609+
"abgeraten. Es ist meist einfacher, Import-Hooks (siehe :pep:`302`) zu "
4610+
"verwenden, um dieselben Ziele zu erreichen, ohne Probleme bei Code zu "
4611+
"verursachen, der von der Standard-Importimplementierung ausgeht. Auch "
4612+
"von der direkten Verwendung von :func:`__import__` wird zugunsten von "
4613+
":func:`importlib.import_module` abgeraten."
46114614

46124615
#: ../../library/functions.rst:2336
46134616
msgid ""
@@ -4635,11 +4638,12 @@ msgid ""
46354638
"directory of the module calling :func:`__import__` (see :pep:`328` for the "
46364639
"details)."
46374640
msgstr ""
4638-
"*level* gibt an, ob absolute oder relative Importe verwendet werden sollen. "
4639-
"``0`` (der Standard) bedeutet, dass nur absolute Importe durchgeführt "
4640-
"werden. Positive Werte für „level“ geben die Anzahl der übergeordneten "
4641-
"Verzeichnisse an, die ausgehend vom Verzeichnis des Moduls, das __import__() "
4642-
"aufruft, durchsucht werden sollen. (siehe :pep:`328` für mehr Details)."
4641+
"*level* gibt an, ob absolute oder relative Importe verwendet werden "
4642+
"sollen. ``0`` (der Standard) bedeutet, dass nur absolute Importe "
4643+
"durchgeführt werden. Positive Werte für *level* geben die Anzahl der "
4644+
"übergeordneten Verzeichnisse an, die ausgehend vom Verzeichnis des "
4645+
"Moduls, das :func:`__import__` aufruft, durchsucht werden sollen (siehe "
4646+
":pep:`328` für die Details)."
46434647

46444648
#: ../../library/functions.rst:2349
46454649
msgid ""

library/string.po

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1807,7 +1807,7 @@ msgstr ""
18071807

18081808
#: ../../library/string.rst:911
18091809
msgid "Here is an example of how to use a Template::"
1810-
msgstr "Hier ist ein Beispiel für die Verwendung von :class:`Template`::"
1810+
msgstr "Hier ist ein Beispiel für die Verwendung eines Template::"
18111811

18121812
#: ../../library/string.rst:913
18131813
msgid ""

po_status.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/usr/bin/env python3
2+
"""
3+
po_status.py — Listet alle .po-Dateien in einem Verzeichnisbaum mit
4+
Übersetzungsstand (x/y), Prozent, Dateigröße und Pfad und Status Gesamtübersetzung.
5+
"""
6+
import argparse
7+
import re
8+
import subprocess
9+
import sys
10+
from pathlib import Path
11+
12+
STATS_RE = re.compile(
13+
r"(?:(\d+) translated messages?)?"
14+
r"(?:, (\d+) fuzzy translations?)?"
15+
r"(?:, (\d+) untranslated messages?)?"
16+
)
17+
18+
19+
def get_stats(po_file: Path):
20+
"""Ruft msgfmt --statistics auf und parst das Ergebnis."""
21+
try:
22+
result = subprocess.run(
23+
["msgfmt", "--statistics", "-o", "/dev/null", str(po_file)],
24+
capture_output=True, text=True, timeout=15,
25+
)
26+
except FileNotFoundError:
27+
sys.exit("Fehler: 'msgfmt' wurde nicht gefunden. Ist gettext installiert "
28+
"(z.B. 'brew install gettext' + PATH-Anpassung)?")
29+
30+
output = (result.stderr or "") + (result.stdout or "")
31+
if "error" in output.lower() and "translated" not in output.lower():
32+
return None # Syntaxfehler in der Datei
33+
34+
m = STATS_RE.search(output)
35+
if not m:
36+
return {"translated": 0, "fuzzy": 0, "untranslated": 0}
37+
translated, fuzzy, untranslated = (int(x) if x else 0 for x in m.groups())
38+
return {"translated": translated, "fuzzy": fuzzy, "untranslated": untranslated}
39+
40+
41+
def human_size(num_bytes: int) -> str:
42+
for unit in ("B", "KB", "MB"):
43+
if num_bytes < 1024:
44+
return f"{num_bytes:.0f} {unit}" if unit == "B" else f"{num_bytes:.1f} {unit}"
45+
num_bytes /= 1024
46+
return f"{num_bytes:.1f} GB"
47+
48+
49+
def main():
50+
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
51+
parser.add_argument("path", nargs="?", default=".", help="Wurzelverzeichnis (Standard: .)")
52+
parser.add_argument("--exclude", default="c-api,.venv,.git",
53+
help="Kommagetrennte Ordnernamen, die übersprungen werden")
54+
parser.add_argument("--sort", choices=["path", "percent", "size"], default="percent")
55+
parser.add_argument("--max-percent", type=float, default=100.1,
56+
help="Nur Dateien mit weniger als diesem Prozentwert anzeigen")
57+
args = parser.parse_args()
58+
59+
root = Path(args.path).resolve()
60+
excludes = {e.strip() for e in args.exclude.split(",") if e.strip()}
61+
62+
po_files = sorted(
63+
p for p in root.rglob("*.po")
64+
if not excludes.intersection(p.relative_to(root).parts)
65+
)
66+
67+
if not po_files:
68+
print(f"Keine .po-Dateien unter {root} gefunden.")
69+
return
70+
71+
rows = []
72+
for po_file in po_files:
73+
stats = get_stats(po_file)
74+
rel_path = po_file.relative_to(root)
75+
size = po_file.stat().st_size
76+
if stats is None:
77+
rows.append((str(rel_path), "FEHLER (Syntax)", -1.0, size))
78+
continue
79+
total = stats["translated"] + stats["fuzzy"] + stats["untranslated"]
80+
pct = (stats["translated"] / total * 100) if total else 100.0
81+
label = f"{stats['translated']}/{total}"
82+
if stats["fuzzy"]:
83+
label += f" ({stats['fuzzy']} fuzzy)"
84+
rows.append((str(rel_path), label, pct, size))
85+
86+
# Gesamtstatistik wird VOR dem --max-percent-Filter berechnet (über alle Dateien)
87+
complete_count = sum(1 for r in rows if r[2] >= 100.0)
88+
error_count = sum(1 for r in rows if r[2] < 0)
89+
total_translated = sum(int(r[1].split("/")[0]) for r in rows if r[2] >= 0)
90+
total_strings = sum(int(r[1].split("/")[1].split(" ")[0]) for r in rows if r[2] >= 0)
91+
overall_pct = (total_translated / total_strings * 100) if total_strings else 0.0
92+
93+
rows = [r for r in rows if r[2] < args.max_percent or r[2] < 0]
94+
95+
if not rows:
96+
print(f"Alle {len(po_files)} Dateien liegen bei/über --max-percent {args.max_percent} "
97+
f"— nichts anzuzeigen.")
98+
return
99+
100+
if args.sort == "percent":
101+
rows.sort(key=lambda r: r[2], reverse=True) # 100% -> 0%
102+
elif args.sort == "size":
103+
rows.sort(key=lambda r: -r[3])
104+
105+
path_w = max(len(r[0]) for r in rows) + 2
106+
label_w = max(len(r[1]) for r in rows) + 2
107+
108+
print(f"{'Pfad':<{path_w}}{'Übersetzt':<{label_w}}{'%':>7} {'Größe':>8}")
109+
print("-" * (path_w + label_w + 20))
110+
for rel_path, label, pct, size in rows:
111+
pct_str = " n/a" if pct < 0 else f"{pct:6.1f}%"
112+
print(f"{rel_path:<{path_w}}{label:<{label_w}}{pct_str:>7} {human_size(size):>8}")
113+
114+
print("-" * (path_w + label_w + 20))
115+
print(f"{len(rows)} von {len(po_files)} Dateien angezeigt "
116+
f"(Filter: --max-percent {args.max_percent})")
117+
print()
118+
print(f"Vollständig übersetzt (100%): {complete_count} von {len(po_files)} Dateien")
119+
if error_count:
120+
print(f"Dateien mit Syntaxfehler: {error_count}")
121+
print(f"Gesamtstand über alle Dateien: {total_translated}/{total_strings} Strings "
122+
f"({overall_pct:.2f}%)")
123+
124+
125+
if __name__ == "__main__":
126+
main()

0 commit comments

Comments
 (0)