@@ -450,7 +450,7 @@ def option_help(mlist, varhelp):
450450 item = None
451451 reflist = varhelp .split ('/' )
452452 if len (reflist ) >= 2 :
453- category = subcat = None
453+ category , subcat = None , None
454454 if len (reflist ) == 2 :
455455 category , varname = reflist
456456 elif len (reflist ) == 3 :
@@ -514,18 +514,59 @@ def option_help(mlist, varhelp):
514514 doc .AddItem (mlist .GetMailmanFooter ())
515515 print (doc .Format ())
516516
517+ def add_standard_headers (doc , mlist , title , category = None , subcat = None ):
518+ """Add standard headers to admin pages.
519+
520+ Args:
521+ doc: The Document object
522+ mlist: The MailList object
523+ title: The page title
524+ category: Optional category name
525+ subcat: Optional subcategory name
526+ """
527+ # Set the page title
528+ doc .SetTitle (title )
529+
530+ # Add the main header
531+ doc .AddItem (Header (2 , title ))
532+
533+ # Add navigation breadcrumbs if category/subcat provided
534+ if category :
535+ adminurl = mlist .GetScriptURL ('admin' )
536+ categories = mlist .GetConfigCategories ()
537+ category_label = _ (categories [category ][0 ])
538+ if isinstance (category_label , bytes ):
539+ category_label = category_label .decode (Utils .GetCharSet (mlist .preferred_language ), 'replace' )
540+
541+ breadcrumbs = []
542+ breadcrumbs .append (Link (adminurl , _ ('Admin' )))
543+ breadcrumbs .append (Link (f'{ adminurl } /{ category } ' , category_label ))
544+
545+ if subcat :
546+ subcat_label = _ (categories [category ][1 ].get (subcat , [subcat ])[0 ])
547+ if isinstance (subcat_label , bytes ):
548+ subcat_label = subcat_label .decode (Utils .GetCharSet (mlist .preferred_language ), 'replace' )
549+ breadcrumbs .append (Link (f'{ adminurl } /{ category } /{ subcat } ' , subcat_label ))
550+
551+ doc .AddItem (Center (' > ' .join (breadcrumbs )))
552+
553+ # Add horizontal rule
554+ doc .AddItem ('<hr>' )
555+
517556def show_results (mlist , doc , category , subcat , cgidata ):
518557 # Produce the results page
519558 adminurl = mlist .GetScriptURL ('admin' )
520559 categories = mlist .GetConfigCategories ()
521560 label = _ (categories [category ][0 ])
522561 if isinstance (label , bytes ):
523- label = label .decode ('latin1' , 'replace' )
562+ label = label .decode (Utils . GetCharSet ( mlist . preferred_language ) , 'replace' )
524563
525- doc .SetTitle (_ ('%(realname)s Administration (%(label)s)' ) % {
564+ # Add standard headers
565+ title = _ ('%(realname)s Administration (%(label)s)' ) % {
526566 'realname' : mlist .real_name ,
527567 'label' : label
528- })
568+ }
569+ add_standard_headers (doc , mlist , title , category , subcat )
529570
530571 # Use ParseTags for the main content
531572 replacements = {
@@ -539,6 +580,11 @@ def show_results(mlist, doc, category, subcat, cgidata):
539580 'rmlisturl' : mlist .GetScriptURL ('rmlist' ) if mm_cfg .OWNERS_CAN_DELETE_THEIR_OWN_LISTS and mlist .internal_name () != mm_cfg .MAILMAN_SITE_LIST else None
540581 }
541582
583+ # Ensure all replacements are properly encoded for the list's language
584+ for key , value in replacements .items ():
585+ if isinstance (value , bytes ):
586+ replacements [key ] = value .decode (Utils .GetCharSet (mlist .preferred_language ), 'replace' )
587+
542588 output = mlist .ParseTags ('admin_results.html' , replacements , mlist .preferred_language )
543589 doc .AddItem (output )
544590
@@ -586,7 +632,7 @@ def show_variables(mlist, category, subcat, cgidata, doc):
586632 mailman_log ('debug' , 'Got config categories: %s' , str (categories ))
587633 label = _ (categories [category ][0 ])
588634 if isinstance (label , bytes ):
589- label = label .decode ('latin1' , 'replace' )
635+ label = label .decode (Utils . GetCharSet ( mlist . preferred_language ) , 'replace' )
590636 mailman_log ('debug' , 'Category label: %s' , label )
591637
592638 table .AddRow ([Center (Header (2 , label ))])
@@ -597,7 +643,7 @@ def show_variables(mlist, category, subcat, cgidata, doc):
597643 # description if it is a string
598644 description = options [0 ]
599645 if isinstance (description , bytes ):
600- description = description .decode ('latin1' , 'replace' )
646+ description = description .decode (Utils . GetCharSet ( mlist . preferred_language ) , 'replace' )
601647 mailman_log ('debug' , 'Description: %s' , description )
602648 if type (description ) is str :
603649 table .AddRow ([description ])
@@ -623,7 +669,7 @@ def show_variables(mlist, category, subcat, cgidata, doc):
623669 # treated as a general description, while any others are
624670 # treated as section headers - centered and italicized...
625671 if isinstance (item , bytes ):
626- item = item .decode ('latin1' , 'replace' )
672+ item = item .decode (Utils . GetCharSet ( mlist . preferred_language ) , 'replace' )
627673 formatted_text = '[%s]' % item
628674 item = Bold (formatted_text ).Format ()
629675 table .AddRow ([Center (Italic (item ))])
@@ -707,16 +753,7 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra):
707753 kind == mm_cfg .Host or kind == mm_cfg .Number ):
708754 # Ensure value is a string, decoding bytes if necessary
709755 if isinstance (value , bytes ):
710- try :
711- # Try UTF-8 first
712- value = value .decode ('utf-8' , 'replace' )
713- except UnicodeDecodeError :
714- try :
715- # Try EUC-JP for Japanese text
716- value = value .decode ('euc-jp' , 'replace' )
717- except UnicodeDecodeError :
718- # Fall back to latin1
719- value = value .decode ('latin1' , 'replace' )
756+ value = value .decode (Utils .GetCharSet (mlist .preferred_language ), 'replace' )
720757 return TextBox (varname , value , params )
721758 elif kind == mm_cfg .Text :
722759 if params :
@@ -725,16 +762,7 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra):
725762 r , c = None , None
726763 # Ensure value is a string, decoding bytes if necessary
727764 if isinstance (value , bytes ):
728- try :
729- # Try UTF-8 first
730- value = value .decode ('utf-8' , 'replace' )
731- except UnicodeDecodeError :
732- try :
733- # Try EUC-JP for Japanese text
734- value = value .decode ('euc-jp' , 'replace' )
735- except UnicodeDecodeError :
736- # Fall back to latin1
737- value = value .decode ('latin1' , 'replace' )
765+ value = value .decode (Utils .GetCharSet (mlist .preferred_language ), 'replace' )
738766 return TextArea (varname , value or '' , r , c )
739767 elif kind in (mm_cfg .EmailList , mm_cfg .EmailListEx ):
740768 if params :
@@ -743,16 +771,7 @@ def get_item_gui_value(mlist, category, kind, varname, params, extra):
743771 r , c = None , None
744772 # Ensure value is a string, decoding bytes if necessary
745773 if isinstance (value , bytes ):
746- try :
747- # Try UTF-8 first
748- value = value .decode ('utf-8' , 'replace' )
749- except UnicodeDecodeError :
750- try :
751- # Try EUC-JP for Japanese text
752- value = value .decode ('euc-jp' , 'replace' )
753- except UnicodeDecodeError :
754- # Fall back to latin1
755- value = value .decode ('latin1' , 'replace' )
774+ value = value .decode (Utils .GetCharSet (mlist .preferred_language ), 'replace' )
756775 res = NL .join (value )
757776 return TextArea (varname , res , r , c , wrap = 'off' )
758777 elif kind == mm_cfg .FileUpload :
@@ -819,11 +838,11 @@ def makebox(i, name, pattern, desc, empty=False, table=table):
819838 data = getattr (mlist , varname )
820839 for name , pattern , desc , empty in data :
821840 if isinstance (name , bytes ):
822- name = name .decode ('latin1' , 'replace' )
841+ name = name .decode (Utils . GetCharSet ( mlist . preferred_language ) , 'replace' )
823842 if isinstance (pattern , bytes ):
824- pattern = pattern .decode ('latin1' , 'replace' )
843+ pattern = pattern .decode (Utils . GetCharSet ( mlist . preferred_language ) , 'replace' )
825844 if isinstance (desc , bytes ):
826- desc = desc .decode ('latin1' , 'replace' )
845+ desc = desc .decode (Utils . GetCharSet ( mlist . preferred_language ) , 'replace' )
827846 makebox (i , name , pattern , desc , empty )
828847 i += 1
829848 # Add one more non-deleteable widget as the first blank entry, but
@@ -889,7 +908,7 @@ def makebox(i, pattern, action, empty=False, table=table):
889908 data = getattr (mlist , varname )
890909 for pattern , action , empty in data :
891910 if isinstance (pattern , bytes ):
892- pattern = pattern .decode ('latin1' , 'replace' )
911+ pattern = pattern .decode (Utils . GetCharSet ( mlist . preferred_language ) , 'replace' )
893912 makebox (i , pattern , action , empty )
894913 i += 1
895914 # Add one more non-deleteable widget as the first blank entry, but
@@ -942,43 +961,24 @@ def membership_options(mlist, subcat, cgidata, doc, form):
942961 adminurl = mlist .GetScriptURL ('admin' , absolute = 1 )
943962 container = Container ()
944963 header = Table (width = "100%" )
945- # If we're in the list subcategory, show the membership list
964+
965+ # Add standard headers based on subcat
946966 if subcat == 'add' :
947- header .AddRow ([Center (Header (2 , _ ('Mass Subscriptions' )))])
948- header .AddCellInfo (header .GetCurrentRowIndex (), 0 , colspan = 2 ,
949- bgcolor = mm_cfg .WEB_HEADER_COLOR )
950- container .AddItem (header )
951- mass_subscribe (mlist , container )
952- return container
953- if subcat == 'remove' :
954- header .AddRow ([Center (Header (2 , _ ('Mass Removals' )))])
955- header .AddCellInfo (header .GetCurrentRowIndex (), 0 , colspan = 2 ,
956- bgcolor = mm_cfg .WEB_HEADER_COLOR )
957- container .AddItem (header )
958- mass_remove (mlist , container )
959- return container
960- if subcat == 'change' :
961- header .AddRow ([Center (Header (2 , _ ('Address Change' )))])
962- header .AddCellInfo (header .GetCurrentRowIndex (), 0 , colspan = 2 ,
963- bgcolor = mm_cfg .WEB_HEADER_COLOR )
964- container .AddItem (header )
965- address_change (mlist , container )
966- return container
967- if subcat == 'sync' :
968- header .AddRow ([Center (Header (2 , _ ('Sync Membership List' )))])
969- header .AddCellInfo (header .GetCurrentRowIndex (), 0 , colspan = 2 ,
970- bgcolor = mm_cfg .WEB_HEADER_COLOR )
971- container .AddItem (header )
972- mass_sync (mlist , container )
973- return container
974- # Otherwise...
975- header .AddRow ([Center (Header (2 , _ ('Membership List' )))])
976- header .AddCellInfo (header .GetCurrentRowIndex (), 0 , colspan = 2 ,
977- bgcolor = mm_cfg .WEB_HEADER_COLOR )
978- container .AddItem (header )
967+ title = _ ('Mass Subscriptions' )
968+ elif subcat == 'remove' :
969+ title = _ ('Mass Removals' )
970+ elif subcat == 'change' :
971+ title = _ ('Address Change' )
972+ elif subcat == 'sync' :
973+ title = _ ('Sync Membership List' )
974+ else :
975+ title = _ ('Membership List' )
976+
977+ add_standard_headers (doc , mlist , title , 'members' , subcat )
978+
979979 # Add a "search for member" button
980980 table = Table (width = '100%' )
981- link = Link ('https://docs.python.org/2 /library/re.html'
981+ link = Link ('https://docs.python.org/3 /library/re.html'
982982 '#regular-expression-syntax' ,
983983 _ ('(help)' )).Format ()
984984 table .AddRow ([Label (_ ('Find member %(link)s:' ) % {'link' : link }),
0 commit comments