bulkrax 9.3.4 → 9.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -1
  3. data/app/assets/javascripts/bulkrax/application.js +2 -1
  4. data/app/assets/javascripts/bulkrax/bulkrax.js +13 -4
  5. data/app/assets/javascripts/bulkrax/bulkrax_utils.js +96 -0
  6. data/app/assets/javascripts/bulkrax/datatables.js +1 -0
  7. data/app/assets/javascripts/bulkrax/entries.js +17 -10
  8. data/app/assets/javascripts/bulkrax/importers.js.erb +9 -2
  9. data/app/assets/javascripts/bulkrax/importers_stepper.js +2420 -0
  10. data/app/assets/stylesheets/bulkrax/application.css +1 -1
  11. data/app/assets/stylesheets/bulkrax/import_export.scss +9 -2
  12. data/app/assets/stylesheets/bulkrax/stepper/_header.scss +83 -0
  13. data/app/assets/stylesheets/bulkrax/stepper/_mixins.scss +26 -0
  14. data/app/assets/stylesheets/bulkrax/stepper/_navigation.scss +103 -0
  15. data/app/assets/stylesheets/bulkrax/stepper/_responsive.scss +46 -0
  16. data/app/assets/stylesheets/bulkrax/stepper/_review.scss +92 -0
  17. data/app/assets/stylesheets/bulkrax/stepper/_settings.scss +106 -0
  18. data/app/assets/stylesheets/bulkrax/stepper/_success.scss +26 -0
  19. data/app/assets/stylesheets/bulkrax/stepper/_summary.scss +171 -0
  20. data/app/assets/stylesheets/bulkrax/stepper/_upload.scss +339 -0
  21. data/app/assets/stylesheets/bulkrax/stepper/_validation.scss +237 -0
  22. data/app/assets/stylesheets/bulkrax/stepper/_variables.scss +46 -0
  23. data/app/assets/stylesheets/bulkrax/stepper.scss +32 -0
  24. data/app/controllers/bulkrax/guided_imports_controller.rb +175 -0
  25. data/app/controllers/bulkrax/importers_controller.rb +34 -28
  26. data/app/controllers/concerns/bulkrax/guided_import_demo_scenarios.rb +201 -0
  27. data/app/controllers/concerns/bulkrax/importer_file_handler.rb +217 -0
  28. data/app/factories/bulkrax/object_factory.rb +3 -2
  29. data/app/factories/bulkrax/valkyrie_object_factory.rb +61 -17
  30. data/app/jobs/bulkrax/export_work_job.rb +1 -3
  31. data/app/jobs/bulkrax/importer_job.rb +11 -4
  32. data/app/models/bulkrax/csv_entry.rb +27 -7
  33. data/app/models/bulkrax/entry.rb +4 -0
  34. data/app/models/bulkrax/importer.rb +31 -1
  35. data/app/models/concerns/bulkrax/has_matchers.rb +2 -2
  36. data/app/models/concerns/bulkrax/importer_exporter_behavior.rb +6 -5
  37. data/app/parsers/bulkrax/application_parser.rb +31 -5
  38. data/app/parsers/bulkrax/csv_parser.rb +42 -10
  39. data/app/parsers/concerns/bulkrax/csv_parser/csv_template_generation.rb +73 -0
  40. data/app/parsers/concerns/bulkrax/csv_parser/csv_validation.rb +133 -0
  41. data/app/parsers/concerns/bulkrax/csv_parser/csv_validation_helpers.rb +282 -0
  42. data/app/parsers/concerns/bulkrax/csv_parser/csv_validation_hierarchy.rb +96 -0
  43. data/app/services/bulkrax/csv_template/column_builder.rb +60 -0
  44. data/app/services/bulkrax/csv_template/column_descriptor.rb +58 -0
  45. data/app/services/bulkrax/csv_template/csv_builder.rb +83 -0
  46. data/app/services/bulkrax/csv_template/explanation_builder.rb +57 -0
  47. data/app/services/bulkrax/csv_template/field_analyzer.rb +56 -0
  48. data/app/services/bulkrax/csv_template/file_path_generator.rb +47 -0
  49. data/app/services/bulkrax/csv_template/file_validator.rb +68 -0
  50. data/app/services/bulkrax/csv_template/mapping_manager.rb +55 -0
  51. data/app/services/bulkrax/csv_template/model_loader.rb +50 -0
  52. data/app/services/bulkrax/csv_template/row_builder.rb +35 -0
  53. data/app/services/bulkrax/csv_template/schema_analyzer.rb +70 -0
  54. data/app/services/bulkrax/csv_template/split_formatter.rb +44 -0
  55. data/app/services/bulkrax/csv_template/value_determiner.rb +68 -0
  56. data/app/services/bulkrax/stepper_response_formatter.rb +347 -0
  57. data/app/services/bulkrax/validation_error_csv_builder.rb +99 -0
  58. data/app/validators/bulkrax/csv_row/child_reference.rb +56 -0
  59. data/app/validators/bulkrax/csv_row/circular_reference.rb +71 -0
  60. data/app/validators/bulkrax/csv_row/controlled_vocabulary.rb +74 -0
  61. data/app/validators/bulkrax/csv_row/duplicate_identifier.rb +63 -0
  62. data/app/validators/bulkrax/csv_row/missing_source_identifier.rb +31 -0
  63. data/app/validators/bulkrax/csv_row/parent_reference.rb +59 -0
  64. data/app/validators/bulkrax/csv_row/required_values.rb +64 -0
  65. data/app/views/bulkrax/entries/_parsed_metadata.html.erb +1 -1
  66. data/app/views/bulkrax/entries/_raw_metadata.html.erb +1 -1
  67. data/app/views/bulkrax/entries/show.html.erb +6 -6
  68. data/app/views/bulkrax/exporters/_form.html.erb +19 -43
  69. data/app/views/bulkrax/exporters/edit.html.erb +2 -2
  70. data/app/views/bulkrax/exporters/index.html.erb +5 -5
  71. data/app/views/bulkrax/exporters/new.html.erb +3 -5
  72. data/app/views/bulkrax/exporters/show.html.erb +3 -3
  73. data/app/views/bulkrax/guided_imports/new.html.erb +567 -0
  74. data/app/views/bulkrax/importers/_bagit_fields.html.erb +9 -9
  75. data/app/views/bulkrax/importers/_browse_everything.html.erb +1 -1
  76. data/app/views/bulkrax/importers/_csv_fields.html.erb +11 -11
  77. data/app/views/bulkrax/importers/_edit_form_buttons.html.erb +23 -23
  78. data/app/views/bulkrax/importers/_edit_item_buttons.html.erb +2 -2
  79. data/app/views/bulkrax/importers/_file_uploader.html.erb +3 -3
  80. data/app/views/bulkrax/importers/_form.html.erb +4 -5
  81. data/app/views/bulkrax/importers/_oai_fields.html.erb +8 -18
  82. data/app/views/bulkrax/importers/_xml_fields.html.erb +13 -13
  83. data/app/views/bulkrax/importers/edit.html.erb +2 -2
  84. data/app/views/bulkrax/importers/index.html.erb +19 -14
  85. data/app/views/bulkrax/importers/new.html.erb +10 -9
  86. data/app/views/bulkrax/importers/show.html.erb +23 -7
  87. data/app/views/bulkrax/importers/upload_corrected_entries.html.erb +6 -6
  88. data/app/views/bulkrax/shared/_bulkrax_errors.html.erb +11 -11
  89. data/app/views/bulkrax/shared/_bulkrax_field_mapping.html.erb +3 -3
  90. data/config/i18n-tasks.yml +195 -0
  91. data/config/locales/bulkrax.de.yml +504 -0
  92. data/config/locales/bulkrax.en.yml +487 -28
  93. data/config/locales/bulkrax.es.yml +504 -0
  94. data/config/locales/bulkrax.fr.yml +504 -0
  95. data/config/locales/bulkrax.it.yml +504 -0
  96. data/config/locales/bulkrax.pt-BR.yml +504 -0
  97. data/config/locales/bulkrax.zh.yml +503 -0
  98. data/config/routes.rb +10 -0
  99. data/lib/bulkrax/data/demo_scenarios.json +2235 -0
  100. data/lib/bulkrax/version.rb +1 -1
  101. data/lib/bulkrax.rb +31 -3
  102. data/lib/tasks/bulkrax_tasks.rake +0 -102
  103. metadata +55 -3
  104. /data/{app/services → lib}/wings/custom_queries/find_by_source_identifier.rb +0 -0
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bulkrax
4
+ module CsvRow
5
+ ##
6
+ # Detects circular parent-child relationships in the CSV.
7
+ # A circular reference occurs when following the parent chain from any record
8
+ # eventually leads back to itself (e.g. A→B→C→A).
9
+ #
10
+ # The validator builds a directed graph (child → parents) from all records on
11
+ # first invocation and caches the set of all record ids involved in any cycle.
12
+ # Subsequent per-row calls simply check membership in that set.
13
+ #
14
+ # Requires context key:
15
+ # :relationship_graph – Hash { source_identifier => [parent_ids] } built by
16
+ # run_row_validators before iterating rows.
17
+ module CircularReference
18
+ def self.call(record, row_index, context)
19
+ cycle_ids = context[:circular_reference_ids] ||= detect_cycle_ids(context[:relationship_graph] || {})
20
+ return unless cycle_ids.include?(record[:source_identifier])
21
+
22
+ context[:errors] << {
23
+ row: row_index,
24
+ source_identifier: record[:source_identifier],
25
+ severity: 'error',
26
+ category: 'circular_reference',
27
+ column: 'parents',
28
+ value: record[:source_identifier],
29
+ message: I18n.t('bulkrax.importer.guided_import.validation.circular_reference_validator.errors.message',
30
+ value: record[:source_identifier]),
31
+ suggestion: I18n.t('bulkrax.importer.guided_import.validation.circular_reference_validator.errors.suggestion')
32
+ }
33
+ end
34
+
35
+ # Returns the set of all source identifiers that participate in at least one cycle.
36
+ # Uses recursive DFS with a per-branch ancestry set to detect back-edges.
37
+ def self.detect_cycle_ids(graph)
38
+ all_nodes = graph.keys.to_set | graph.values.flatten.to_set
39
+ visited = Set.new
40
+ cycle_ids = Set.new
41
+
42
+ all_nodes.each do |node|
43
+ next if visited.include?(node)
44
+ dfs(node, graph, visited, [], cycle_ids)
45
+ end
46
+
47
+ cycle_ids
48
+ end
49
+ private_class_method :detect_cycle_ids
50
+
51
+ def self.dfs(node, graph, visited, ancestors, cycle_ids) # rubocop:disable Metrics/MethodLength
52
+ visited.add(node)
53
+ ancestors.push(node)
54
+
55
+ (graph[node] || []).each do |neighbor|
56
+ if ancestors.include?(neighbor)
57
+ # Back-edge found: mark every node in the cycle path
58
+ cycle_start = ancestors.index(neighbor)
59
+ ancestors[cycle_start..].each { |n| cycle_ids.add(n) }
60
+ cycle_ids.add(neighbor)
61
+ elsif !visited.include?(neighbor)
62
+ dfs(neighbor, graph, visited, ancestors, cycle_ids)
63
+ end
64
+ end
65
+
66
+ ancestors.pop
67
+ end
68
+ private_class_method :dfs
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bulkrax
4
+ module CsvRow
5
+ ##
6
+ # Validates that controlled vocabulary values in each row are valid according
7
+ # to the QA authority for that field.
8
+ module ControlledVocabulary
9
+ def self.call(record, row_index, context) # rubocop:disable Metrics/MethodLength
10
+ field_metadata = context[:field_metadata]
11
+ return if field_metadata.blank?
12
+
13
+ model = record[:model]
14
+ metadata = field_metadata[model]
15
+ return if metadata.blank?
16
+
17
+ controlled_terms = metadata[:controlled_vocab_terms] || []
18
+ return if controlled_terms.blank?
19
+
20
+ controlled_terms.each do |field|
21
+ value = record[:raw_row][field]
22
+ next if value.blank?
23
+
24
+ authority = load_authority(field)
25
+ next if authority.nil?
26
+
27
+ term = authority.find(value)
28
+ next unless term.blank? || term.dig('active') == false
29
+
30
+ context[:errors] << {
31
+ row: row_index,
32
+ source_identifier: record[:source_identifier],
33
+ severity: 'error',
34
+ category: 'invalid_controlled_value',
35
+ column: field,
36
+ value: value,
37
+ message: I18n.t('bulkrax.importer.guided_import.validation.controlled_vocabulary_validator.errors.message',
38
+ value: value, field: field),
39
+ suggestion: suggestion(value, authority)
40
+ }
41
+ end
42
+ end
43
+
44
+ def self.load_authority(field)
45
+ Qa::Authorities::Local.subauthority_for(field.pluralize)
46
+ rescue Qa::InvalidSubAuthority
47
+ begin
48
+ Qa::Authorities::Local.subauthority_for(field)
49
+ rescue Qa::InvalidSubAuthority
50
+ nil
51
+ end
52
+ end
53
+ private_class_method :load_authority
54
+
55
+ def self.suggestion(value, authority)
56
+ suggestion = DidYouMean::SpellChecker.new(dictionary: dictionary_for(authority)).correct(value).first
57
+ return fallback_suggestion if suggestion.nil?
58
+
59
+ I18n.t('bulkrax.importer.guided_import.validation.did_you_mean', suggestion: suggestion)
60
+ end
61
+ private_class_method :suggestion
62
+
63
+ def self.fallback_suggestion
64
+ I18n.t('bulkrax.importer.guided_import.validation.controlled_vocabulary_validator.errors.suggestion')
65
+ end
66
+ private_class_method :fallback_suggestion
67
+
68
+ def self.dictionary_for(authority)
69
+ authority.all.filter_map { |term| term['label'] if term['active'] == true }.uniq
70
+ end
71
+ private_class_method :dictionary_for
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bulkrax
4
+ module CsvRow
5
+ ##
6
+ # Validates that each row has a unique source_identifier.
7
+ # Uses context[:seen_ids] (Hash: id => first_seen_row_number) to detect duplicates.
8
+ module DuplicateIdentifier
9
+ def self.call(record, row_index, context)
10
+ source_id = record[:source_identifier]
11
+ return if source_id.blank? && Bulkrax.fill_in_blank_source_identifiers.present?
12
+
13
+ source_id_label = context[:source_identifier] || 'source_identifier'
14
+ first_row = context[:seen_ids][source_id]
15
+
16
+ if first_row
17
+ add_duplicate_error(context, row_index, source_id, source_id_label, first_row)
18
+ else
19
+ context[:seen_ids][source_id] = row_index
20
+ add_existing_warning(context, row_index, source_id, source_id_label)
21
+ end
22
+ end
23
+
24
+ def self.add_duplicate_error(context, row_index, source_id, source_id_label, first_row)
25
+ context[:errors] << {
26
+ row: row_index,
27
+ source_identifier: source_id,
28
+ severity: 'error',
29
+ category: 'duplicate_source_identifier',
30
+ column: source_id_label,
31
+ value: source_id,
32
+ message: I18n.t('bulkrax.importer.guided_import.validation.duplicate_identifier_validator.errors.message',
33
+ value: source_id,
34
+ field: source_id_label,
35
+ original_row: first_row),
36
+ suggestion: I18n.t('bulkrax.importer.guided_import.validation.duplicate_identifier_validator.errors.suggestion',
37
+ field: source_id_label)
38
+ }
39
+ end
40
+ private_class_method :add_duplicate_error
41
+
42
+ def self.add_existing_warning(context, row_index, source_id, source_id_label)
43
+ find_record = context[:find_record_by_source_identifier]
44
+ return unless find_record&.call(source_id)
45
+
46
+ context[:errors] << {
47
+ row: row_index,
48
+ source_identifier: source_id,
49
+ severity: 'warning',
50
+ category: 'existing_source_identifier',
51
+ column: source_id_label,
52
+ value: source_id,
53
+ message: I18n.t('bulkrax.importer.guided_import.validation.existing_source_identifier_validator.warnings.message',
54
+ value: source_id,
55
+ field: source_id_label),
56
+ suggestion: I18n.t('bulkrax.importer.guided_import.validation.existing_source_identifier_validator.warnings.suggestion',
57
+ field: source_id_label)
58
+ }
59
+ end
60
+ private_class_method :add_existing_warning
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bulkrax
4
+ module CsvRow
5
+ ##
6
+ # Validates that each row has a value for source_identifier unless
7
+ # fill_in_blank_source_identifiers is configured (in which case Bulkrax
8
+ # will generate one automatically).
9
+ module MissingSourceIdentifier
10
+ def self.call(record, row_index, context)
11
+ return if Bulkrax.fill_in_blank_source_identifiers.present?
12
+ return if record[:source_identifier].present?
13
+
14
+ source_id_label = context[:source_identifier] || 'source_identifier'
15
+
16
+ context[:errors] << {
17
+ row: row_index,
18
+ source_identifier: nil,
19
+ severity: 'error',
20
+ category: 'missing_source_identifier',
21
+ column: source_id_label,
22
+ value: nil,
23
+ message: I18n.t('bulkrax.importer.guided_import.validation.missing_source_identifier_validator.errors.message',
24
+ field: source_id_label),
25
+ suggestion: I18n.t('bulkrax.importer.guided_import.validation.missing_source_identifier_validator.errors.suggestion',
26
+ field: source_id_label)
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bulkrax
4
+ module CsvRow
5
+ ##
6
+ # Validates that any parent references in a row point to source identifiers
7
+ # that exist either elsewhere in the same CSV or as existing repository records.
8
+ # Uses context[:all_ids] (Set of all source identifiers) to validate references
9
+ # within the CSV, and context[:find_record_by_source_identifier] (callable) to
10
+ # look up existing records in the same way the importer does at runtime.
11
+ # Uses context[:parent_split_pattern] (String/Regexp, may be nil) for multi-value splitting.
12
+ module ParentReference
13
+ def self.call(record, row_index, context)
14
+ all_ids = context[:all_ids]
15
+ find_record = context[:find_record_by_source_identifier]
16
+
17
+ collect_parent_ids(record, context).each do |parent_id|
18
+ next if all_ids.include?(parent_id)
19
+ next if find_record&.call(parent_id)
20
+
21
+ context[:errors] << {
22
+ row: row_index,
23
+ source_identifier: record[:source_identifier],
24
+ severity: 'error',
25
+ category: 'invalid_parent_reference',
26
+ column: 'parent',
27
+ value: parent_id,
28
+ message: I18n.t('bulkrax.importer.guided_import.validation.parent_reference_validator.errors.message',
29
+ value: parent_id,
30
+ field: 'source_identifier'),
31
+ suggestion: I18n.t('bulkrax.importer.guided_import.validation.parent_reference_validator.errors.suggestion')
32
+ }
33
+ end
34
+ end
35
+
36
+ def self.collect_parent_ids(record, context)
37
+ split_pattern = context[:parent_split_pattern]
38
+ parent_column = context[:parent_column] || 'parents'
39
+
40
+ base_ids = if split_pattern
41
+ record[:parent].to_s.split(split_pattern).map(&:strip).reject(&:blank?)
42
+ elsif record[:parent].present?
43
+ [record[:parent].to_s.strip]
44
+ else
45
+ []
46
+ end
47
+
48
+ suffix_pattern = /\A#{Regexp.escape(parent_column)}_\d+\z/
49
+ suffix_ids = record[:raw_row]
50
+ .select { |k, _| k.to_s.match?(suffix_pattern) }
51
+ .values
52
+ .map(&:to_s).map(&:strip).reject(&:blank?)
53
+
54
+ (base_ids + suffix_ids).uniq
55
+ end
56
+ private_class_method :collect_parent_ids
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bulkrax
4
+ module CsvRow
5
+ ##
6
+ # Validates that each row provides a value for every required field of its model.
7
+ # Numeric suffixes on column names are normalised before checking
8
+ # (e.g. 'title_1' satisfies the 'title' requirement).
9
+ module RequiredValues
10
+ def self.call(record, row_index, context)
11
+ field_metadata = context[:field_metadata]
12
+ return if field_metadata.blank?
13
+
14
+ using_default = record[:model].blank?
15
+ model = record[:model].presence || Bulkrax.default_work_type
16
+ metadata = field_metadata[model]
17
+ return if metadata.blank?
18
+
19
+ add_default_work_type_warning(context, record, row_index, model) if using_default
20
+ add_missing_required_value_errors(context, record, row_index, metadata)
21
+ end
22
+
23
+ def self.add_default_work_type_warning(context, record, row_index, model)
24
+ # Suppress per-row warning when a file-level notice already covers all rows.
25
+ return if context[:notices]&.any? { |n| n[:field] == 'model' }
26
+
27
+ context[:errors] << {
28
+ row: row_index,
29
+ source_identifier: record[:source_identifier],
30
+ severity: 'warning',
31
+ category: 'default_work_type_used',
32
+ column: 'model',
33
+ value: nil,
34
+ message: I18n.t('bulkrax.importer.guided_import.validation.default_work_type_validator.warnings.message',
35
+ default_work_type: model),
36
+ suggestion: I18n.t('bulkrax.importer.guided_import.validation.default_work_type_validator.warnings.suggestion')
37
+ }
38
+ end
39
+ private_class_method :add_default_work_type_warning
40
+
41
+ def self.add_missing_required_value_errors(context, record, row_index, metadata)
42
+ (metadata[:required_terms] || []).each do |field|
43
+ next if record[:raw_row].any? { |key, value| normalize_header(key.to_s) == field && value.present? }
44
+
45
+ context[:errors] << {
46
+ row: row_index,
47
+ source_identifier: record[:source_identifier],
48
+ severity: 'error',
49
+ category: 'missing_required_value',
50
+ column: field,
51
+ value: nil,
52
+ message: I18n.t('bulkrax.importer.guided_import.validation.required_field_validator.errors.message', field: field),
53
+ suggestion: I18n.t('bulkrax.importer.guided_import.validation.required_field_validator.errors.suggestion', field: field)
54
+ }
55
+ end
56
+ end
57
+ private_class_method :add_missing_required_value_errors
58
+
59
+ def self.normalize_header(header)
60
+ header.sub(/_\d+\z/, '')
61
+ end
62
+ end
63
+ end
64
+ end
@@ -2,7 +2,7 @@
2
2
  <div class="accordion-container">
3
3
  <div class="accordion-heading" role="tab" id="parsed-metadata-heading">
4
4
  <a class="accordion-title" role="button" data-toggle="collapse" data-target="#parsed-metadata-show" aria-expanded="true" aria-controls="parsed-metadata-show">
5
- Parsed Metadata:
5
+ <%= t('bulkrax.entry.labels.parsed_metadata') %>:
6
6
  </a>
7
7
  <a role="button" data-toggle="collapse" data-target="#parsed-metadata-show" aria-expanded="true" aria-controls="parsed-metadata-show">
8
8
  <div class="accordion-icon fa fa-times-circle" aria-hidden="true"></div>
@@ -2,7 +2,7 @@
2
2
  <div class="accordion-container">
3
3
  <div class="accordion-heading" role="tab" id="raw-metadata-heading">
4
4
  <a class="accordion-title" role="button" data-toggle="collapse" data-target="#raw-metadata-show" aria-expanded="true" aria-controls="raw-metadata-show">
5
- Raw Metadata:
5
+ <%= t('bulkrax.entry.labels.raw_metadata') %>:
6
6
  </a>
7
7
  <a role="button" data-toggle="collapse" data-target="#raw-metadata-show" aria-expanded="true" aria-controls="raw-metadata-show">
8
8
  <div class="accordion-icon fa fa-times-circle" aria-hidden="true"></div>
@@ -13,7 +13,7 @@
13
13
 
14
14
  <p class='bulkrax-p-align'>
15
15
  <strong><%= t('bulkrax.importer.labels.type') %>:</strong>
16
- <%= @entry.factory_class || 'Unknown' %>
16
+ <%= @entry.factory_class || t('bulkrax.entry.labels.unknown') %>
17
17
  </p>
18
18
  <%= render partial: 'raw_metadata'%>
19
19
 
@@ -39,29 +39,29 @@
39
39
  <% if factory_record.present? %>
40
40
  <% factory_record_class = factory_record.class %>
41
41
  <% factory_record_class_human = factory_record_class.model_name.human %>
42
- <strong><%= factory_record_class_human %> Link:</strong>
42
+ <strong><%= t('bulkrax.entry.labels.record_link', record_type: factory_record_class_human) %>:</strong>
43
43
  <% if defined?(Hyrax) && factory_record_class_human == 'Collection' %>
44
44
  <%= link_to factory_record_class_human, hyrax.polymorphic_path(factory_record) %>
45
45
  <% else %>
46
46
  <%= link_to factory_record_class_human, main_app.polymorphic_path(factory_record) %>
47
47
  <% end %>
48
48
  <% else %>
49
- <strong>Item Link:</strong> Item has not yet been imported successfully
49
+ <strong><%= t('bulkrax.entry.labels.item_link') %>:</strong> <%= t('bulkrax.entry.labels.item_not_imported') %>
50
50
  <% end %>
51
51
  <% rescue => e %>
52
- <strong>Item Link:</strong> Unable to retrieve item (<%= e.message %>)
52
+ <strong><%= t('bulkrax.entry.labels.item_link') %>:</strong> <%= t('bulkrax.entry.labels.item_link_error', message: e.message) %>
53
53
  <% end %>
54
54
  <% else %>
55
55
  <% record = @entry&.hyrax_record %>
56
56
  <% if record.present? && @entry.factory_class %>
57
- <strong><%= record.model_name.human %> Link:</strong>
57
+ <strong><%= t('bulkrax.entry.labels.record_link', record_type: record.model_name.human) %>:</strong>
58
58
  <% if defined?(Hyrax) && record.model_name.human == "Collection" %>
59
59
  <%= link_to record.model_name.human, hyrax.polymorphic_path(record) %>
60
60
  <% else %>
61
61
  <%= link_to record.model_name.human, main_app.polymorphic_path(record) %>
62
62
  <% end %>
63
63
  <% else %>
64
- <strong>Item Link:</strong> No item associated with this entry or class unknown
64
+ <strong><%= t('bulkrax.entry.labels.item_link') %>:</strong> <%= t('bulkrax.entry.labels.item_no_association') %>
65
65
  <% end %>
66
66
  <% end %>
67
67
  </p>
@@ -1,7 +1,7 @@
1
1
  <div class="panel-body card-body">
2
2
  <% if exporter.errors.any? %>
3
3
  <div id="error_explanation">
4
- <h2><%= pluralize(exporter.errors.count, "error") %> prohibited this exporter from being saved:</h2>
4
+ <h2><%= t('bulkrax.exporter.validations.errors_prohibited', count: exporter.errors.count) %></h2>
5
5
 
6
6
  <ul>
7
7
  <% exporter.errors.full_messages.each do |message| %>
@@ -11,35 +11,31 @@
11
11
  </div>
12
12
  <% end %>
13
13
 
14
- <%= form.input :name, label: t('bulkrax.exporter.labels.name'), input_html: { class: 'form-control' } %>
14
+ <%= form.input :name, input_html: { class: 'form-control' } %>
15
15
 
16
16
  <%= form.hidden_field :user_id, value: current_user.id %>
17
17
 
18
18
  <%= form.input :export_type,
19
- collection: form.object.export_type_list,
20
- label: t('bulkrax.exporter.labels.export_type'),
19
+ collection: form.object.export_type_list,
21
20
  required: true,
22
- prompt: 'Please select an export type',
21
+ prompt: t('bulkrax.exporter.prompts.export_type'),
23
22
  input_html: { class: 'form-control' } %>
24
23
 
25
24
  <%= form.input :export_from,
26
25
  collection: form.object.export_from_list,
27
- label: t('bulkrax.exporter.labels.export_from'),
28
26
  required: true,
29
- prompt: 'Please select an export source',
27
+ prompt: t('bulkrax.exporter.prompts.export_from'),
30
28
  input_html: { class: 'form-control' } %>
31
29
 
32
30
  <%= form.input :export_source_importer,
33
- label: t('bulkrax.exporter.labels.importer'),
34
31
  required: true,
35
- prompt: 'Select from the list',
32
+ prompt: t('bulkrax.exporter.prompts.select_from_list'),
36
33
  label_html: { class: 'importer export-source-option d-none hidden' },
37
34
  input_html: { class: 'importer export-source-option d-none hidden form-control' },
38
- collection: form.object.importers_list.sort %>
35
+ collection: form.object.importers_list.sort %>
39
36
 
40
37
  <%= form.input :export_source_collection,
41
- prompt: 'Start typing ...',
42
- label: t('bulkrax.exporter.labels.collection'),
38
+ prompt: t('bulkrax.exporter.prompts.start_typing'),
43
39
  required: true,
44
40
  placeholder: @collection&.title&.first,
45
41
  label_html: { class: 'collection export-source-option d-none hidden' },
@@ -53,61 +49,41 @@
53
49
  %>
54
50
 
55
51
  <%= form.input :export_source_worktype,
56
- label: t('bulkrax.exporter.labels.worktype'),
57
52
  required: true,
58
- prompt: 'Select from the list',
53
+ prompt: t('bulkrax.exporter.prompts.select_from_list'),
59
54
  label_html: { class: 'worktype export-source-option d-none hidden' },
60
55
  input_html: { class: 'worktype export-source-option d-none hidden form-control' },
61
56
  collection: Bulkrax.curation_concerns.map { |cc| [cc.to_s, cc.to_s] } %>
62
57
 
63
58
  <%= form.input :limit,
64
59
  as: :integer,
65
- hint: 'leave blank or 0 for all records',
66
- label: t('bulkrax.exporter.labels.limit'),
67
60
  input_html: { class: 'form-control' } %>
68
61
 
69
- <%= form.input :generated_metadata?,
70
- as: :boolean,
71
- label: t('bulkrax.exporter.labels.generated_metadata'),
72
- hint: t('bulkrax.exporter.hints.generated_metadata') %>
62
+ <%= form.input :generated_metadata?, as: :boolean %>
73
63
 
74
- <%= form.input :include_thumbnails?,
75
- as: :boolean,
76
- label: t('bulkrax.exporter.labels.include_thumbnails'),
77
- hint: t('bulkrax.exporter.hints.include_thumbnails') %>
64
+ <%= form.input :include_thumbnails?, as: :boolean %>
78
65
 
79
- <%= form.input :date_filter,
80
- as: :boolean,
81
- label: t('bulkrax.exporter.labels.filter_by_date') %>
66
+ <%= form.input :date_filter, as: :boolean %>
82
67
 
83
68
  <div id="date_filter_picker" class="d-none hidden">
84
- <%= form.input :start_date,
85
- as: :date,
86
- label: t('bulkrax.exporter.labels.start_date'),
87
- input_html: { class: 'form-control' } %>
88
-
89
- <%= form.input :finish_date,
90
- as: :date,
91
- label: t('bulkrax.exporter.labels.finish_date'),
92
- input_html: { class: 'form-control' } %>
69
+ <%= form.input :start_date, as: :date, input_html: { class: 'form-control' } %>
70
+
71
+ <%= form.input :finish_date, as: :date, input_html: { class: 'form-control' } %>
93
72
  </div>
94
73
  <% if defined?(::Hyrax) %>
95
74
  <%= form.input :work_visibility,
96
- collection: form.object.work_visibility_list,
97
- label: t('bulkrax.exporter.labels.visibility'),
98
- input_html: { class: 'form-control' } %>
75
+ collection: form.object.work_visibility_list,
76
+ input_html: { class: 'form-control' } %>
99
77
  <% end %>
100
78
 
101
79
  <% if defined?(::Hyrax) %>
102
80
  <%= form.input :workflow_status,
103
- collection: form.object.workflow_status_list,
104
- label: t('bulkrax.exporter.labels.status'),
105
- input_html: { class: 'form-control' } %>
81
+ collection: form.object.workflow_status_list,
82
+ input_html: { class: 'form-control' } %>
106
83
  <% end %>
107
84
 
108
85
  <%= form.input :parser_klass,
109
86
  collection: Bulkrax.parsers.map {|p| [p[:name], p[:class_name], {'data-partial' => p[:partial]}] if p[:class_name].constantize.export_supported? }.compact,
110
- label: t('bulkrax.exporter.labels.export_format'),
111
87
  input_html: { class: 'form-control' } %>
112
88
  </div>
113
89
 
@@ -9,9 +9,9 @@
9
9
  <%= render 'form', exporter: @exporter, form: form %>
10
10
  <div class="panel-footer card-footer bulkrax-card-footer">
11
11
  <div class='pull-right'>
12
- <%= form.button :submit, value: 'Update Exporter', class: 'btn btn-primary' %>
12
+ <%= form.button :submit, value: t('helpers.action.exporter.update'), class: 'btn btn-primary' %>
13
13
  |
14
- <%= form.button :submit, value: 'Update and Re-Export All Items', class: 'btn btn-primary' %>
14
+ <%= form.button :submit, value: t('helpers.action.exporter.update_and_re_export'), class: 'btn btn-primary' %>
15
15
  |
16
16
  <% cancel_path = form.object.persisted? ? exporter_path(form.object) : exporters_path %>
17
17
  <%= link_to t('bulkrax.cancel'), cancel_path, class: 'btn btn-default ' %>
@@ -17,11 +17,11 @@
17
17
  <table id='exporters-table' class="table table-striped">
18
18
  <thead>
19
19
  <tr>
20
- <th scope="col">Name</th>
21
- <th scope="col">Status</th>
22
- <th scope="col">Date Exported</th>
23
- <th scope="col">Downloadable Files</th>
24
- <th scope="col">Actions</th>
20
+ <th scope="col"><%= t('bulkrax.table_header.labels.name') %></th>
21
+ <th scope="col"><%= t('bulkrax.table_header.labels.status') %></th>
22
+ <th scope="col"><%= t('bulkrax.table_header.labels.date_exported') %></th>
23
+ <th scope="col"><%= t('bulkrax.table_header.labels.downloadable_files') %></th>
24
+ <th scope="col"><%= t('bulkrax.table_header.labels.actions') %></th>
25
25
  </tr>
26
26
  </thead>
27
27
  </table>
@@ -9,12 +9,10 @@
9
9
  <%= render 'form', exporter: @exporter, form: form %>
10
10
  <div class="panel-footer card-footer bulkrax-card-footer">
11
11
  <div class='pull-right'>
12
- <%= form.button :submit, value: 'Create and Export', class: 'btn btn-primary' %>
13
- |
14
- <%= form.button :submit, value: 'Create', class: 'btn btn-primary' %>
15
- |
12
+ <%= submit_tag t('helpers.action.exporter.create_and_export'), class: 'btn btn-primary me-2' %>
13
+ <%= submit_tag t('helpers.action.exporter.create'), class: 'btn btn-default me-2' %>
16
14
  <% cancel_path = form.object.persisted? ? exporter_path(form.object) : exporters_path %>
17
- <%= link_to t('bulkrax.cancel'), cancel_path, class: 'btn btn-default ' %>
15
+ <%= link_to t('bulkrax.cancel'), cancel_path, class: 'btn btn-default' %>
18
16
  </div>
19
17
  </div>
20
18
  <% end %>
@@ -11,7 +11,7 @@
11
11
  <%= simple_form_for @exporter, method: :get, url: exporter_download_path(@exporter), html: { class: 'form-inline bulkrax-p-align' } do |form| %>
12
12
  <strong>Download:</strong>
13
13
  <%= render 'downloads', exporter: @exporter, form: form %>
14
- <%= form.button :submit, value: 'Download', data: { disable_with: false } %>
14
+ <%= form.button :submit, value: t('helpers.action.exporter.download'), data: { disable_with: false } %>
15
15
  <% end %>
16
16
  <% end %>
17
17
 
@@ -101,9 +101,9 @@
101
101
  <%= render partial: 'bulkrax/shared/entries_tab', locals: { item: @exporter } %>
102
102
  </div>
103
103
  <br>
104
- <%= link_to 'Edit', edit_exporter_path(@exporter) %>
104
+ <%= link_to t('helpers.action.exporter.edit'), edit_exporter_path(@exporter) %>
105
105
  |
106
- <%= link_to 'Back', exporters_path %>
106
+ <%= link_to t('helpers.action.exporter.back'), exporters_path %>
107
107
  </div>
108
108
  </div>
109
109
  </div>