@@ -114,23 +114,48 @@ def _detect_license_from_pyproject(root: Path) -> Optional[str]:
114114
115115
116116def _detect_license_from_files (root : Path ) -> Optional [str ]:
117+ """Detect license text from common files and folders in a robust, case-insensitive way.
118+ Looks for top-level files like LICENSE/LICENCE/COPYING/NOTICE (any case, with .txt/.md/.rst),
119+ accepts prefixed variants (e.g., LICENSE-APACHE), and scans LICENSES/LICENCES/LEGAL directories.
120+ Returns up to the first 8 KiB of detected license text.
121+ """
117122 try :
118- candidates = [
119- "LICENSE" ,
120- "LICENSE.txt" ,
121- "LICENSE.md" ,
122- "LICENCE" ,
123- "LICENCE.txt" ,
124- "COPYING" ,
125- "COPYING.txt" ,
126- ]
127- for name in candidates :
128- p = root / name
129- if p .is_file ():
123+ names = ("license" , "licence" , "copying" , "copyright" , "unlicense" , "notice" )
124+ exts = ("" , ".txt" , ".md" , ".rst" )
125+ # Top-level files
126+ try :
127+ for p in sorted (root .iterdir ()):
130128 try :
131- return p .read_text (encoding = "utf-8" , errors = "ignore" )[:8192 ]
129+ if not p .is_file ():
130+ continue
131+ low = p .name .lower ()
132+ stem = p .stem .lower ()
133+ suf = p .suffix .lower ()
134+ if (stem in names and (suf in exts )) or any (
135+ low .startswith (prefix + "-" ) for prefix in ("license" , "licence" )
136+ ):
137+ try :
138+ return p .read_text (encoding = "utf-8" , errors = "ignore" )[:8192 ]
139+ except Exception :
140+ continue
132141 except Exception :
133142 continue
143+ except Exception :
144+ pass
145+ # Common license folders
146+ for dname in ("licenses" , "licences" , "license" , "licence" , "legal" , "LEGAL" , "LICENSES" , "LICENCES" ):
147+ d = root / dname
148+ if not d .is_dir ():
149+ continue
150+ # Prefer obvious files first
151+ patterns = ("*LICENSE*" , "*LICENCE*" , "*COPYING*" , "*NOTICE*" , "*" )
152+ for pat in patterns :
153+ for p in sorted (d .glob (pat )):
154+ try :
155+ if p .is_file ():
156+ return p .read_text (encoding = "utf-8" , errors = "ignore" )[:8192 ]
157+ except Exception :
158+ continue
134159 except Exception :
135160 return None
136161 return None
@@ -210,7 +235,10 @@ def on_pre_compile(self, ctx: PreCompileContext) -> None:
210235 spdx_id , lic_name = _detect_workspace_license (sctx .workspace_root )
211236 if not spdx_id :
212237 sctx .log_info (
213- "license_injector: aucune licence détectée dans le workspace (pyproject/LICEN[SC]E/COPYING). Aucune injection."
238+ sctx .tr (
239+ "license_injector: aucune licence détectée (pyproject ou fichiers LICENSE/LICENCE/COPYING/NOTICE). Aucune injection." ,
240+ "license_injector: no license detected (pyproject or LICENSE/LICENCE/COPYING/NOTICE files). No injection performed." ,
241+ )
214242 )
215243 return
216244
@@ -224,22 +252,30 @@ def on_pre_compile(self, ctx: PreCompileContext) -> None:
224252
225253 # Demande confirmation
226254 if not sctx .msg_question (
227- "License Injector" ,
228- f"Injecter la licence (SPDX: { spdx_id } ) dans les fichiers correspondant aux motifs: { patterns } ?" ,
255+ sctx .tr ("License Injector" , "License Injector" ),
256+ sctx .tr (
257+ f"Injecter la licence (SPDX: { spdx_id } ) dans les fichiers correspondant aux motifs: { patterns } ?" ,
258+ f"Inject license (SPDX: { spdx_id } ) into files matching: { patterns } ?" ,
259+ ),
229260 default_yes = False ,
230261 ):
231- sctx .log_warn ("license_injector: opération annulée par l'utilisateur" )
262+ sctx .log_warn (sctx . tr ( "license_injector: opération annulée par l'utilisateur" , "license_injector: operation canceled by user" ) )
232263 return
233264
234265 # Phase 1: analyse des cibles
235- ph = API_SDK .progress ("Injection de licence" , "Analyse des fichiers cibles..." , maximum = 0 , cancelable = True )
266+ ph = API_SDK .progress (
267+ sctx .tr ("Injection de licence" , "License Injection" ),
268+ sctx .tr ("Analyse des fichiers cibles..." , "Scanning target files..." ),
269+ maximum = 0 ,
270+ cancelable = True ,
271+ )
236272 try :
237273 to_modify : list [Path ] = []
238274 found = 0
239275 skipped_dup = 0
240276 for p in sctx .iter_files (patterns , exclude = exclude , enforce_workspace = True ):
241277 if ph .canceled :
242- sctx .log_warn ("license_injector: opération annulée par l'utilisateur" )
278+ sctx .log_warn (sctx . tr ( "license_injector: opération annulée par l'utilisateur" , "license_injector: operation canceled by user" ) )
243279 return
244280 found += 1
245281 try :
@@ -274,7 +310,7 @@ def on_pre_compile(self, ctx: PreCompileContext) -> None:
274310 changed = 0
275311 for i , p in enumerate (to_modify , start = 1 ):
276312 if ph .canceled :
277- sctx .log_warn ("license_injector: opération annulée par l'utilisateur" )
313+ sctx .log_warn (sctx . tr ( "license_injector: opération annulée par l'utilisateur" , "license_injector: operation canceled by user" ) )
278314 return
279315 rel = p .relative_to (sctx .workspace_root )
280316 ph .update (i , f"{ i } /{ len (to_modify )} : { rel } " )
@@ -292,7 +328,10 @@ def on_pre_compile(self, ctx: PreCompileContext) -> None:
292328 sctx .log_warn (f"Écriture échouée pour { rel } : { e } " )
293329
294330 sctx .log_info (
295- f"license_injector: terminé. Modifiés={ changed } , déjà avec SPDX={ skipped_dup } , total scannés={ found } "
331+ sctx .tr (
332+ f"license_injector: terminé. Modifiés={ changed } , déjà avec SPDX={ skipped_dup } , total scannés={ found } " ,
333+ f"license_injector: done. Changed={ changed } , already SPDX={ skipped_dup } , total scanned={ found } " ,
334+ )
296335 )
297336 finally :
298337 ph .close ()
0 commit comments