1- from pathlib import Path
21import json
3- from typing import TYPE_CHECKING , TypeAlias , TypedDict , Annotated as A
2+ from pathlib import Path
3+ from typing import TYPE_CHECKING , TypeAlias , TypedDict
4+ from typing import Annotated as A
45
6+ import numpy as np
7+ import plotly .graph_objects as go
58from babel .messages import pofile
69from docutils import nodes
710from docutils .parsers .rst import Directive
8- import plotly .graph_objects as go
911from plotly .offline import plot
10- import numpy as np
1112
1213if TYPE_CHECKING :
1314 from sphinx .application import Sphinx
1718LOCALES_DIR = BASE_DIR / "locales" # Locales directory
1819STATIC_DIR = BASE_DIR / "_static" # Static directory
1920
21+
2022class ModuleStats (TypedDict ):
2123 total : int
2224 translated : int
2325 fuzzy : int
2426 untranslated : int
2527 percentage : float
2628
27- TranslationStats : TypeAlias = dict [A [str , "locale" ], dict [A [str , "module" ], ModuleStats ]]
29+
30+ TranslationStats : TypeAlias = dict [
31+ A [str , "locale" ], dict [A [str , "module" ], ModuleStats ]
32+ ]
2833
2934
3035class TranslationGraph (Directive ):
@@ -42,19 +47,39 @@ class TranslationGraph(Directive):
4247 Total: %{customdata.total}<br>
4348 Completed: %{customdata.percentage}%
4449 """
50+
4551 def run (self ):
4652 data = get_translation_stats ()
4753
4854 # Sort data by locale and module
49- data = {locale : dict (sorted (loc_stats .items ())) for locale , loc_stats in sorted (data .items ())}
55+ data = {
56+ locale : dict (sorted (loc_stats .items ()))
57+ for locale , loc_stats in sorted (data .items ())
58+ }
5059
5160 # prepend english, everything set to 100%
52- en = {module : ModuleStats (total = stats ['total' ], translated = stats ['total' ], fuzzy = stats ['total' ], untranslated = 0 , percentage = 100 ) for module , stats in next (iter (data .values ())).items ()}
53- data = {'en' : en } | data
61+ en = {
62+ module : ModuleStats (
63+ total = stats ["total" ],
64+ translated = stats ["total" ],
65+ fuzzy = stats ["total" ],
66+ untranslated = 0 ,
67+ percentage = 100 ,
68+ )
69+ for module , stats in next (iter (data .values ())).items ()
70+ }
71+ data = {"en" : en } | data
5472
5573 # Calculate average completion percentage for each locale and sort locales
56- locale_completion = {locale : np .mean ([stats ['percentage' ] for stats in loc_stats .values ()]) for locale , loc_stats in data .items ()}
57- sorted_locales = sorted (locale_completion .keys (), key = lambda locale : locale_completion [locale ], reverse = True )
74+ locale_completion = {
75+ locale : np .mean ([stats ["percentage" ] for stats in loc_stats .values ()])
76+ for locale , loc_stats in data .items ()
77+ }
78+ sorted_locales = sorted (
79+ locale_completion .keys (),
80+ key = lambda locale : locale_completion [locale ],
81+ reverse = True ,
82+ )
5883
5984 # Reorder data based on sorted locales
6085 data = {locale : data [locale ] for locale in sorted_locales }
@@ -64,11 +89,20 @@ def run(self):
6489 modules = list (next (iter (data .values ())).keys ())
6590
6691 # Extract data to plot
67- values = [[stats ['percentage' ] for stats in loc_stats .values ()] for loc_stats in data .values ()]
68- hoverdata = [[{'module' : module } | stats for module , stats in loc_stats .items ()] for loc_stats in data .values ()]
92+ values = [
93+ [stats ["percentage" ] for stats in loc_stats .values ()]
94+ for loc_stats in data .values ()
95+ ]
96+ hoverdata = [
97+ [{"module" : module } | stats for module , stats in loc_stats .items ()]
98+ for loc_stats in data .values ()
99+ ]
69100
70101 # Add text to display percentages directly in the heatmap boxes
71- text = [[f"{ int (stats ['percentage' ])} %" for stats in loc_stats .values ()] for loc_stats in data .values ()]
102+ text = [
103+ [f"{ int (stats ['percentage' ])} %" for stats in loc_stats .values ()]
104+ for loc_stats in data .values ()
105+ ]
72106
73107 heatmap = go .Heatmap (
74108 x = modules ,
@@ -83,23 +117,28 @@ def run(self):
83117 hovertemplate = self .HOVER_TEMPLATE ,
84118 name = "" , # Set the trace name to an empty string to remove "trace 0" from hoverbox
85119 colorbar = {
86- ' orientation' : 'h' ,
87- 'y' : 0 ,
120+ " orientation" : "h" ,
121+ "y" : 0 ,
88122 "yanchor" : "bottom" ,
89123 "yref" : "container" ,
90124 "title" : "Completion %" ,
91125 "thickness" : 10 ,
92126 "tickvals" : [12.5 , 50 , 87.5 , 100 ], # Midpoints for each category
93- "ticktext" : ["0-25%" , "25-75%" , "75-<100%" , "100%" ], # Labels for categories
127+ "ticktext" : [
128+ "0-25%" ,
129+ "25-75%" ,
130+ "75-<100%" ,
131+ "100%" ,
132+ ], # Labels for categories
94133 },
95134 colorscale = [
96- [0.0 , "rgb(254, 255, 231)" ], # 0-25%
135+ [0.0 , "rgb(254, 255, 231)" ], # 0-25%
97136 [0.25 , "rgb(254, 255, 231)" ],
98- [0.25 , "rgb(187, 130, 176)" ], # 25-75%
137+ [0.25 , "rgb(187, 130, 176)" ], # 25-75%
99138 [0.75 , "rgb(187, 130, 176)" ],
100- [0.75 , "rgb(129, 192, 170)" ], # 75-<100%
139+ [0.75 , "rgb(129, 192, 170)" ], # 75-<100%
101140 [0.99 , "rgb(129, 192, 170)" ],
102- [1.0 , "rgb(78, 112, 100)" ], # 100%
141+ [1.0 , "rgb(78, 112, 100)" ], # 100%
103142 ],
104143 )
105144 # Create figure
@@ -112,7 +151,7 @@ def run(self):
112151 xaxis_showgrid = False ,
113152 xaxis_side = "top" ,
114153 xaxis_tickangle = - 45 ,
115- xaxis_tickfont = {
154+ xaxis_tickfont = {
116155 "family" : "var(--bs-font-monospace)" ,
117156 },
118157 yaxis_showgrid = False ,
@@ -127,7 +166,8 @@ def run(self):
127166 )
128167 return [nodes .raw ("" , div , format = "html" )]
129168
130- def calculate_translation_percentage (po_path : Path , locale : str ) -> ModuleStats :
169+
170+ def calculate_translation_percentage (po_path : Path , locale : str ) -> ModuleStats :
131171 """
132172 Calculate the translation percentage for a given .po file.
133173
@@ -170,7 +210,7 @@ def calculate_translation_percentage(po_path : Path, locale : str) -> ModuleStat
170210 "translated" : translated ,
171211 "fuzzy" : fuzzy ,
172212 "untranslated" : total - translated - fuzzy ,
173- "percentage" : round (percentage , 2 )
213+ "percentage" : round (percentage , 2 ),
174214 }
175215
176216
@@ -218,10 +258,16 @@ def get_translation_stats() -> TranslationStats:
218258
219259 return results
220260
261+
221262def write_translation_stats (app : "Sphinx" , exception : Exception | None ) -> None :
222263 from sphinx .util import logging
264+
223265 logger = logging .getLogger ("_ext.translation_graph" )
224266
267+ if app .builder .name != "html" :
268+ logger .info ("Skipping translation stats for non-HTML build" )
269+ return
270+
225271 stats = get_translation_stats ()
226272 out_path = app .outdir / "_static" / "translation_stats.json"
227273 with open (out_path , "w" ) as f :
0 commit comments