@@ -6,7 +6,7 @@ def index
66 # For overview tab - prepare statistics data
77 @available_locales = I18n . available_locales . map ( &:to_s )
88 @available_model_types = collect_all_model_types
9- @available_attributes = collect_available_attributes ( 'all' )
9+ @available_attributes = collect_all_attributes
1010
1111 @data_type_summary = build_data_type_summary
1212 @data_type_stats = calculate_data_type_stats
@@ -16,6 +16,7 @@ def index
1616 @model_type_stats = calculate_model_type_stats
1717 @attribute_stats = calculate_attribute_stats
1818 @total_translation_records = calculate_total_records
19+ @unique_translated_records = calculate_unique_translated_records
1920
2021 # Calculate model instance translation coverage
2122 @model_instance_stats = calculate_model_instance_stats
@@ -98,9 +99,22 @@ def collect_all_model_types
9899 collect_rich_text_translation_models ( model_types )
99100 collect_file_translation_models ( model_types )
100101
101- # Convert to array and constantize
102+ # Convert to array, constantize, and filter for models with actual translatable attributes
102103 model_types . map do |type_name |
103- { name : type_name , class : type_name . constantize }
104+ model_class = type_name . constantize
105+
106+ # Load subclasses to ensure STI descendants are available
107+ load_subclasses ( model_class )
108+
109+ # Check if the model actually has translatable attributes
110+ has_translatable_attributes = has_translatable_attributes? ( model_class )
111+
112+ if has_translatable_attributes
113+ { name : type_name , class : model_class }
114+ else
115+ Rails . logger . debug "Skipping #{ type_name } : no translatable attributes found"
116+ nil
117+ end
104118 rescue StandardError => e
105119 Rails . logger . warn "Could not constantize model type #{ type_name } : #{ e . message } "
106120 nil
@@ -338,20 +352,55 @@ def collect_text_translation_models(model_types)
338352 def collect_rich_text_translation_models ( model_types )
339353 return unless defined? ( ActionText ::RichText )
340354
355+ # Get unique combinations of record_type and name to validate translatable attributes
341356 ActionText ::RichText
342357 . distinct
343- . pluck ( :record_type )
344- . each { |type | model_types . add ( type ) }
358+ . pluck ( :record_type , :name )
359+ . each do |record_type , attribute_name |
360+ next unless record_type . present? && attribute_name . present?
361+
362+ begin
363+ model_class = record_type . constantize
364+ load_subclasses ( model_class )
365+
366+ # Check if this specific attribute is translatable in the model or its descendants
367+ if has_translatable_rich_text_attribute? ( model_class , attribute_name )
368+ model_types . add ( record_type )
369+ else
370+ Rails . logger . debug "Skipping #{ record_type } : attribute '#{ attribute_name } ' not found in translatable rich text attributes"
371+ end
372+ rescue StandardError => e
373+ Rails . logger . warn "Could not check rich text translatability for #{ record_type } : #{ e . message } "
374+ end
375+ end
345376 end
346377
347378 def collect_file_translation_models ( model_types )
348379 return unless defined? ( ActiveStorage ::Attachment ) &&
349380 ActiveStorage ::Attachment . column_names . include? ( 'locale' )
350381
382+ # Get unique combinations of record_type and name to validate translatable attachments
351383 ActiveStorage ::Attachment
384+ . where . not ( locale : [ nil , '' ] ) # Only include records with actual locale values
352385 . distinct
353- . pluck ( :record_type )
354- . each { |type | model_types . add ( type ) }
386+ . pluck ( :record_type , :name )
387+ . each do |record_type , attachment_name |
388+ next unless record_type . present? && attachment_name . present?
389+
390+ begin
391+ model_class = record_type . constantize
392+ load_subclasses ( model_class )
393+
394+ # Check if this specific attachment is translatable in the model or its descendants
395+ if has_translatable_attachment? ( model_class , attachment_name )
396+ model_types . add ( record_type )
397+ else
398+ Rails . logger . debug "Skipping #{ record_type } : attachment '#{ attachment_name } ' not found in translatable attachments"
399+ end
400+ rescue StandardError => e
401+ Rails . logger . warn "Could not check file translatability for #{ record_type } : #{ e . message } "
402+ end
403+ end
355404 end
356405
357406 def group_models_by_namespace ( models )
@@ -624,6 +673,45 @@ def calculate_total_records
624673 count
625674 end
626675
676+ def calculate_unique_translated_records
677+ unique_records = Set . new
678+
679+ # Collect unique (model_type, record_id) pairs from string translations
680+ if defined? ( Mobility ::Backends ::ActiveRecord ::KeyValue ::StringTranslation )
681+ Mobility ::Backends ::ActiveRecord ::KeyValue ::StringTranslation
682+ . distinct
683+ . pluck ( :translatable_type , :translatable_id )
684+ . each { |type , id | unique_records . add ( [ type , id ] ) }
685+ end
686+
687+ # Collect from text translations
688+ if defined? ( Mobility ::Backends ::ActiveRecord ::KeyValue ::TextTranslation )
689+ Mobility ::Backends ::ActiveRecord ::KeyValue ::TextTranslation
690+ . distinct
691+ . pluck ( :translatable_type , :translatable_id )
692+ . each { |type , id | unique_records . add ( [ type , id ] ) }
693+ end
694+
695+ # Collect from rich text translations
696+ if defined? ( ActionText ::RichText )
697+ ActionText ::RichText
698+ . distinct
699+ . pluck ( :record_type , :record_id )
700+ . each { |type , id | unique_records . add ( [ type , id ] ) }
701+ end
702+
703+ # Collect from file translations
704+ if defined? ( ActiveStorage ::Attachment ) && ActiveStorage ::Attachment . column_names . include? ( 'locale' )
705+ ActiveStorage ::Attachment
706+ . where . not ( locale : [ nil , '' ] )
707+ . distinct
708+ . pluck ( :record_type , :record_id )
709+ . each { |type , id | unique_records . add ( [ type , id ] ) }
710+ end
711+
712+ unique_records . size
713+ end
714+
627715 # Calculate unique model instance translation coverage
628716 def calculate_model_instance_stats
629717 stats = { }
@@ -1098,6 +1186,15 @@ def collect_all_attributes
10981186 . each { |attr | attributes . add ( attr ) }
10991187 end
11001188
1189+ # Collect from file translations (ActiveStorage attachments with locale)
1190+ if defined? ( ActiveStorage ::Attachment ) && ActiveStorage ::Attachment . column_names . include? ( 'locale' )
1191+ ActiveStorage ::Attachment
1192+ . where . not ( locale : [ nil , '' ] )
1193+ . distinct
1194+ . pluck ( :name )
1195+ . each { |attr | attributes . add ( attr ) }
1196+ end
1197+
11011198 attributes . to_a . sort
11021199 end
11031200
@@ -1193,5 +1290,68 @@ def load_subclasses(model_class)
11931290 rescue StandardError => e
11941291 Rails . logger . warn "Error loading subclasses for #{ model_class . name } : #{ e . message } "
11951292 end
1293+
1294+ # Check if a model class has any translatable attributes (including STI descendants)
1295+ def has_translatable_attributes? ( model_class )
1296+ # Check mobility attributes on the model itself
1297+ return true if model_class . respond_to? ( :mobility_attributes ) && model_class . mobility_attributes . any?
1298+
1299+ # Check translatable attachments on the model itself
1300+ if model_class . respond_to? ( :mobility_translated_attachments ) && model_class . mobility_translated_attachments &.any?
1301+ return true
1302+ end
1303+
1304+ # For STI models, check descendants
1305+ if model_class . respond_to? ( :descendants ) && model_class . descendants . any?
1306+ model_class . descendants . each do |subclass |
1307+ return true if subclass . respond_to? ( :mobility_attributes ) && subclass . mobility_attributes . any?
1308+ if subclass . respond_to? ( :mobility_translated_attachments ) && subclass . mobility_translated_attachments &.any?
1309+ return true
1310+ end
1311+ end
1312+ end
1313+
1314+ false
1315+ end
1316+
1317+ # Check if a model has a specific translatable rich text attribute
1318+ def has_translatable_rich_text_attribute? ( model_class , attribute_name )
1319+ # Check if the model has this attribute configured for Action Text translation
1320+ if model_class . respond_to? ( :mobility_attributes )
1321+ mobility_configs = model_class . mobility . attributes_hash
1322+ return true if mobility_configs [ attribute_name . to_sym ] &.dig ( :backend ) == :action_text
1323+ end
1324+
1325+ # Check STI descendants
1326+ if model_class . respond_to? ( :descendants ) && model_class . descendants . any?
1327+ model_class . descendants . each do |subclass |
1328+ next unless subclass . respond_to? ( :mobility_attributes )
1329+
1330+ mobility_configs = subclass . mobility . attributes_hash
1331+ return true if mobility_configs [ attribute_name . to_sym ] &.dig ( :backend ) == :action_text
1332+ end
1333+ end
1334+
1335+ false
1336+ end
1337+
1338+ # Check if a model has a specific translatable attachment
1339+ def has_translatable_attachment? ( model_class , attachment_name )
1340+ # Check if the model has this attachment configured as translatable
1341+ if model_class . respond_to? ( :mobility_translated_attachments )
1342+ return model_class . mobility_translated_attachments &.key? ( attachment_name . to_sym )
1343+ end
1344+
1345+ # Check STI descendants
1346+ if model_class . respond_to? ( :descendants ) && model_class . descendants . any?
1347+ model_class . descendants . each do |subclass |
1348+ if subclass . respond_to? ( :mobility_translated_attachments ) && subclass . mobility_translated_attachments &.key? ( attachment_name . to_sym )
1349+ return true
1350+ end
1351+ end
1352+ end
1353+
1354+ false
1355+ end
11961356 end
11971357end
0 commit comments