cocina_display 2.1.0 → 2.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac41e805ba8d1ab521f7d647a0407c7c0cc02840bd3bde547c4ef5a017b32dca
4
- data.tar.gz: ca2d3190ea5e1b9e83f504343fbfc9af462cdd0b4a51e615c623a33ceaef7e21
3
+ metadata.gz: 3465ccfb8142f2351977032932d4e38e8ea97188bbeea980073eb145780c7465
4
+ data.tar.gz: 9a7c59a505bc7eaa88b3ae0cb81becd67f508eeff72d39b9db4725db4a0403e4
5
5
  SHA512:
6
- metadata.gz: 6962854fc5bffc3288692a8feb183f098adba9f3c0f4f424db58d326e87a2d63f534fc06ea644124d6e37fd2fb0846f2970e49348b70f96a4269b2a0f0c53ab0
7
- data.tar.gz: 1650a3791e4236bc2cc9fdbbcd9b4920c670a5c5fb52acc3122b3bea5755efe69d31a61496f3c41f1d8f4defa125791fd38a872d2a75f58055acf5ab41cde0f2
6
+ metadata.gz: cc5c29815d9d5250cfc63eb5546db9eae7374d50f4308089dce8eb7cc5383193ebb125fb4fe0a07226602b3f631fc75aa4fbec78b7796794ad2c27c7c9717217
7
+ data.tar.gz: 7046fcda48d7004206e5b1ee3f724f591f7cc8306c9e560226fb48832ff041f1bdff0c4d28e45d38090e356a8e1375d3da1d39c2ff3a13316e4870d83c7fd0d3
@@ -7,6 +7,7 @@ en:
7
7
  email: Contact information
8
8
  repository: Repository
9
9
  url: Location
10
+ shelf_locator: Shelf locator
10
11
  copyright: Copyright
11
12
  event:
12
13
  date:
@@ -8,7 +8,7 @@ module CocinaDisplay
8
8
  # @return [Array<String>]
9
9
  # @example ["34°03′08″N 118°14′37″W"]
10
10
  def coordinates
11
- coordinate_subject_values.map(&:to_s).compact.uniq
11
+ coordinate_subject_parts.map(&:to_s).compact.uniq
12
12
  end
13
13
 
14
14
  # All valid coordinate data formatted for indexing into a Solr RPT field.
@@ -43,7 +43,7 @@ module CocinaDisplay
43
43
  # @return [Array<String>]
44
44
  # @example ["6252001", "5368361"]
45
45
  def geonames_ids
46
- place_subject_values.map { |s| s.geonames_id }.compact.uniq
46
+ place_subject_parts.map { |s| s.geonames_id }.compact.uniq
47
47
  end
48
48
 
49
49
  private
@@ -54,16 +54,16 @@ module CocinaDisplay
54
54
  all_subjects.filter { |subject| subject.type&.include? "coordinates" }
55
55
  end
56
56
 
57
- # Parsed coordinate values from the coordinate subject values.
57
+ # Parsed coordinate values from the coordinate subject parts.
58
58
  # @return [Array<Geospatial::Coordinates>]
59
59
  def coordinate_objects
60
- coordinate_subject_values.filter_map(&:coordinates)
60
+ coordinate_subject_parts.filter_map(&:coordinates)
61
61
  end
62
62
 
63
- # All subject values that could contain parsed coordinates.
64
- # @return [Array<Subjects::CoordinatesSubjectValue>]
65
- def coordinate_subject_values
66
- subject_values.filter { |s| s.is_a? CocinaDisplay::Subjects::CoordinatesSubjectValue }
63
+ # All subject parts that could contain parsed coordinates.
64
+ # @return [Array<Subjects::CoordinatesSubjectPart>]
65
+ def coordinate_subject_parts
66
+ subject_parts.filter { |s| s.is_a? CocinaDisplay::Subjects::CoordinatesSubjectPart }
67
67
  end
68
68
  end
69
69
  end
@@ -2,10 +2,10 @@ module CocinaDisplay
2
2
  module Concerns
3
3
  # Methods for extracting language information from a Cocina object.
4
4
  module Languages
5
- # Languages objects associated with the object.
6
- # @return [Array<CocinaDisplay::Language>]
5
+ # Languages associated with the object.
6
+ # @return [Array<CocinaDisplay::Languages::Language>]
7
7
  def languages
8
- @languages ||= path("$.description.language.*").map { |lang| CocinaDisplay::Language.new(lang) }
8
+ @languages ||= path("$.description.language.*").map { |lang| CocinaDisplay::Languages::Language.new(lang) }
9
9
  end
10
10
 
11
11
  # Names of languages associated with the object, if recognized by Searchworks.
@@ -5,7 +5,7 @@ module CocinaDisplay
5
5
  # Note objects associated with the cocina record.
6
6
  # @return [Array<CocinaDisplay::Note>]
7
7
  def notes
8
- @notes ||= path("$.description.note.*").map { |note| CocinaDisplay::Note.new(note) }
8
+ @notes ||= path("$.description.note.*").map { |note| CocinaDisplay::Notes::Note.new(note) }
9
9
  end
10
10
 
11
11
  # Text of all abstract notes.
@@ -2,51 +2,51 @@ module CocinaDisplay
2
2
  module Concerns
3
3
  # Methods for extracting and formatting subject information.
4
4
  module Subjects
5
- # All unique subject values that are topics.
5
+ # All unique subject parts that are topics.
6
6
  # @return [Array<String>]
7
7
  def subject_topics
8
- subject_values.filter { |s| s.type == "topic" }.map(&:to_s).uniq
8
+ subject_parts.filter { |s| s.type == "topic" }.map(&:to_s).uniq
9
9
  end
10
10
 
11
- # All unique subject values that are genres.
11
+ # All unique subject parts that are genres.
12
12
  # @return [Array<String>]
13
13
  def subject_genres
14
- subject_values.filter { |s| s.type == "genre" }.map(&:to_s).uniq
14
+ subject_parts.filter { |s| s.type == "genre" }.map(&:to_s).uniq
15
15
  end
16
16
 
17
- # All unique subject values that are titles.
17
+ # All unique subject parts that are titles.
18
18
  # @return [Array<String>]
19
19
  def subject_titles
20
- subject_values.filter { |s| s.type == "title" }.map(&:to_s).uniq
20
+ subject_parts.filter { |s| s.type == "title" }.map(&:to_s).uniq
21
21
  end
22
22
 
23
- # All unique subject values that are date/time info.
23
+ # All unique subject parts that are date/time info.
24
24
  # @return [Array<String>]
25
25
  def subject_temporal
26
- subject_values.filter { |s| s.type == "time" }.map(&:to_s).uniq
26
+ subject_parts.filter { |s| s.type == "time" }.map(&:to_s).uniq
27
27
  end
28
28
 
29
- # All unique subject values that are occupations.
29
+ # All unique subject parts that are occupations.
30
30
  # @return [Array<String>]
31
31
  def subject_occupations
32
- subject_values.filter { |s| s.type == "occupation" }.map(&:to_s).uniq
32
+ subject_parts.filter { |s| s.type == "occupation" }.map(&:to_s).uniq
33
33
  end
34
34
 
35
- # All unique subject values that are named geographic places.
35
+ # All unique subject parts that are named geographic places.
36
36
  # @return [Array<String>]
37
37
  def subject_places
38
- place_subject_values.map(&:to_s).uniq
38
+ place_subject_parts.map(&:to_s).uniq
39
39
  end
40
40
 
41
- # All unique subject values that are names of entities.
41
+ # All unique subject parts that are names of entities.
42
42
  # @note Multiple types are handled: person, family, organization, conference, etc.
43
- # @see CocinaDisplay::NameSubjectValue
43
+ # @see CocinaDisplay::NameSubjectPart
44
44
  # @return [Array<String>]
45
45
  def subject_names
46
- subject_values.filter { |s| s.is_a? CocinaDisplay::Subjects::NameSubjectValue }.map(&:to_s).uniq
46
+ subject_parts.filter { |s| s.is_a? CocinaDisplay::Subjects::NameSubjectPart }.map(&:to_s).uniq
47
47
  end
48
48
 
49
- # Combination of all subject values for searching.
49
+ # Combination of all subject parts for searching.
50
50
  # @see #subject_topics_other
51
51
  # @see #subject_temporal_genre
52
52
  # @see #subject_places
@@ -55,7 +55,7 @@ module CocinaDisplay
55
55
  subject_topics_other + subject_temporal_genre + subject_places
56
56
  end
57
57
 
58
- # Combination of topic, occupation, name, and title subject values for searching.
58
+ # Combination of topic, occupation, name, and title subject parts for searching.
59
59
  # @see #subject_topics
60
60
  # @see #subject_other
61
61
  # @return [Array<String>]
@@ -63,7 +63,7 @@ module CocinaDisplay
63
63
  subject_topics + subject_other
64
64
  end
65
65
 
66
- # Combination of occupation, name, and title subject values for searching.
66
+ # Combination of occupation, name, and title subject parts for searching.
67
67
  # @see #subject_occupations
68
68
  # @see #subject_names
69
69
  # @see #subject_titles
@@ -72,7 +72,7 @@ module CocinaDisplay
72
72
  subject_occupations + subject_names + subject_titles
73
73
  end
74
74
 
75
- # Combination of temporal and genre subject values for searching.
75
+ # Combination of temporal and genre subject parts for searching.
76
76
  # @see #subject_temporal
77
77
  # @see #subject_genres
78
78
  # @return [Array<String>]
@@ -88,7 +88,7 @@ module CocinaDisplay
88
88
  end
89
89
 
90
90
  # Subject data to be rendered for display.
91
- # Uses the concatenated form for structured subject values.
91
+ # Uses the concatenated form for structured subject parts.
92
92
  # @see Subject#to_s
93
93
  # @return [Array<DisplayData>]
94
94
  def subject_display_data
@@ -119,16 +119,16 @@ module CocinaDisplay
119
119
  all_subjects.filter { |subject| subject.type == "classification" }
120
120
  end
121
121
 
122
- # All subject values, flattened from all subjects.
123
- # @return [Array<SubjectValue>]
124
- def subject_values
125
- @subject_values ||= all_subjects.flat_map(&:subject_values)
122
+ # All subject parts, flattened from all subjects.
123
+ # @return [Array<SubjectPart>]
124
+ def subject_parts
125
+ @subject_parts ||= all_subjects.flat_map(&:parallel_values).flat_map(&:subject_parts)
126
126
  end
127
127
 
128
- # All subject values that are named places.
129
- # @return [Array<PlaceSubjectValue>]
130
- def place_subject_values
131
- @place_subject_values ||= subject_values.filter { |s| s.is_a? CocinaDisplay::Subjects::PlaceSubjectValue }
128
+ # All subject parts that are named places.
129
+ # @return [Array<PlaceSubjectPart>]
130
+ def place_subject_parts
131
+ @place_subject_parts ||= subject_parts.filter { |s| s.is_a? CocinaDisplay::Subjects::PlaceSubjectPart }
132
132
  end
133
133
  end
134
134
  end
@@ -34,25 +34,28 @@ module CocinaDisplay
34
34
  primary_title&.sort_title || "\u{10FFFF}"
35
35
  end
36
36
 
37
- # Any additional titles for the object excluding the primary title.
37
+ # Any additional titles for the object excluding the main value of the primary title.
38
+ # Includes parallel titles of the primary title and all titles from secondary titles.
38
39
  # @return [Array<String>]
39
40
  # @see CocinaDisplay::Title#display_title
40
41
  def additional_titles
41
- secondary_titles.map(&:display_title).compact_blank
42
+ (Array(primary_title&.main_value&.siblings) + secondary_titles.flat_map(&:parallel_values))
43
+ .map(&:display_title).compact_blank
42
44
  end
43
45
 
44
- # All {Title} objects, grouped by their label for display.
46
+ # All {TitleValue} objects, grouped by their label for display.
45
47
  # @note All primary titles are included under "Title", not just the first.
46
48
  # @return [Array<DisplayData>]
47
49
  # @param exclude_primary [Boolean] Exclude primary titles. Defaults to false.
48
50
  def title_display_data(exclude_primary: false)
49
- DisplayData.from_objects(exclude_primary ? secondary_titles : all_titles)
51
+ target_titles = exclude_primary ? secondary_titles : all_titles
52
+ DisplayData.from_objects(target_titles.flat_map(&:parallel_values))
50
53
  end
51
54
 
52
55
  # The first title marked primary, or the first without a type.
53
- # @return [Array<Title>]
56
+ # @return [Title, nil]
54
57
  def primary_title
55
- all_titles.find { |title| title.primary? }.presence || all_titles.find { |title| !title.type? }
58
+ all_titles.find { |title| title.primary? }.presence || all_titles.find { |title| !title.typed? }
56
59
  end
57
60
 
58
61
  # All titles except the primary title.
@@ -62,16 +65,14 @@ module CocinaDisplay
62
65
  end
63
66
 
64
67
  # All {Title} objects built from the Cocina titles.
65
- # Flattens parallel values into separate titles.
66
68
  # @return [Array<Title>]
67
69
  def all_titles
68
- @all_titles ||= cocina_titles.flat_map do |cocina_title|
69
- (Array(cocina_title["parallelValue"]).presence || [cocina_title]).map do |value|
70
- Title.new(value, part_label: part_label, part_numbers: part_numbers).tap do |title|
71
- title.type ||= cocina_title["type"]
72
- title.status ||= cocina_title["status"]
73
- end
74
- end
70
+ @all_titles ||= cocina_titles.map do |cocina_title|
71
+ CocinaDisplay::Titles::Title.new(
72
+ cocina_title,
73
+ part_label: part_label,
74
+ part_numbers: part_numbers
75
+ )
75
76
  end
76
77
  end
77
78
 
@@ -19,8 +19,11 @@ module CocinaDisplay
19
19
  def oembed_url(params: {})
20
20
  return if (!is_a?(CocinaDisplay::RelatedResource) && collection?) || purl_url.blank?
21
21
 
22
- params[:url] ||= purl_url
23
- "#{purl_base_url}/embed.json?#{params.to_query}"
22
+ ### Note that searchworks_traject_indexer sends in a single instance of params for all oembed_url calls.
23
+ ### We dup the params to avoid modifying the original hash.
24
+ embed_params = params.dup
25
+ embed_params[:url] ||= purl_url
26
+ "#{purl_base_url}/embed.json?#{embed_params.to_query}"
24
27
  end
25
28
 
26
29
  # The download URL to get the entire object as a .zip file.
@@ -90,7 +90,7 @@ module CocinaDisplay
90
90
  # @param with_date [Boolean] Include life dates, if present
91
91
  # @return [Array<String>]
92
92
  def display_names(with_date: false)
93
- names.map { |name| name.to_s(with_date: with_date) }.compact_blank
93
+ names.flat_map { |name| name.parallel_values.map { |pv| pv.to_s(with_date: with_date) } }.compact_blank
94
94
  end
95
95
 
96
96
  # A single primary name for the contributor.
@@ -117,19 +117,9 @@ module CocinaDisplay
117
117
  end
118
118
 
119
119
  # All names in the Cocina as Name objects.
120
- # Flattens parallel values into separate Name objects.
121
120
  # @return [Array<Name>]
122
121
  def names
123
- @names ||= Array(cocina["name"]).flat_map do |name|
124
- (Array(name["parallelValue"]).presence || [name]).filter_map do |name_value|
125
- unless name_value.blank?
126
- Name.new(name_value).tap do |name_obj|
127
- name_obj.type ||= name["type"]
128
- name_obj.status ||= name["status"]
129
- end
130
- end
131
- end
132
- end
122
+ @names ||= Array(cocina["name"]).map { |name| Name.new(name) }
133
123
  end
134
124
 
135
125
  # All roles in the Cocina structured data.
@@ -2,102 +2,17 @@
2
2
 
3
3
  module CocinaDisplay
4
4
  module Contributors
5
- # A name associated with a contributor.
6
- class Name
7
- # The underlying Cocina structured data for the name.
8
- # @return [Hash]
9
- attr_reader :cocina
10
-
11
- # The type of name, if any (e.g., "personal", "corporate", etc.).
12
- # @return [String, nil]
13
- attr_accessor :type
14
-
15
- # The status of the name, if any (e.g., "primary").
16
- # @return [String, nil]
17
- attr_accessor :status
18
-
19
- # Initialize a Name object with Cocina structured data.
20
- # @param cocina [Hash] The Cocina structured data for the name.
21
- def initialize(cocina)
22
- @cocina = cocina
23
- @type = cocina["type"]
24
- @status = cocina["status"]
25
- end
26
-
27
- # The display string for the name, optionally including life dates.
28
- # @param with_date [Boolean] Include life dates, if present
29
- # @return [String]
30
- # @example no dates
31
- # name.to_s # => "King, Martin Luther, Jr."
32
- # @example with dates
33
- # name.to_s(with_date: true) # => "King, Martin Luther, Jr., 1929-1968"
34
- def to_s(with_date: false)
35
- if cocina["value"].present?
36
- cocina["value"]
37
- elsif dates_str.present? && with_date
38
- Utils.compact_and_join([full_name_str, dates_str], delimiter: ", ")
39
- else
40
- full_name_str
41
- end
42
- end
43
-
44
- # The full name as a string, combining all name components and terms of address.
45
- # @return [String]
46
- def full_name_str
47
- Utils.compact_and_join(name_components.push(terms_of_address_str), delimiter: ", ")
48
- end
49
-
50
- # List of all name components.
51
- # If any of forename, surname, or term of address are present, those are used.
52
- # Otherwise, fall back to any names explicitly marked as "name" or untyped.
53
- # @return [Array<String>]
54
- def name_components
55
- [surname_str, forename_str].compact_blank.presence || Array(name_values["name"])
56
- end
57
-
58
- # Flatten all terms of address into a comma-delimited string.
59
- # @return [String]
60
- def terms_of_address_str
61
- Utils.compact_and_join(Array(name_values["term of address"]), delimiter: ", ")
62
- end
63
-
64
- # Flatten all forename values and ordinals into a whitespace-delimited string.
65
- # @return [String]
66
- def forename_str
67
- Utils.compact_and_join(Array(name_values["forename"]) + Array(name_values["ordinal"]), delimiter: " ")
68
- end
69
-
70
- # Flatten all surname values into a whitespace-delimited string.
71
- # @return [String]
72
- def surname_str
73
- Utils.compact_and_join(Array(name_values["surname"]), delimiter: " ")
74
- end
75
-
76
- # Flatten all life and activity dates into a comma-delimited string.
77
- # @return [String]
78
- def dates_str
79
- Utils.compact_and_join(Array(name_values["life dates"]) + Array(name_values["activity dates"]), delimiter: ", ")
80
- end
81
-
82
- # Is this a primary name for the contributor?
83
- # @return [Boolean]
84
- def primary?
85
- status == "primary"
86
- end
5
+ # A name associated with a contributor, potentially in multiple languages.
6
+ class Name < Parallel::Parallel
7
+ # The name is represented by the main parallel value.
8
+ delegate :to_s, :forename_str, :surname_str, to: :main_value
87
9
 
88
10
  private
89
11
 
90
- # A hash mapping destructured name types to their values.
91
- # Name values with no type are grouped under "name".
92
- # @return [Hash<String, Array<String>>]
93
- # @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#contributor-name-part-types-for-structured-value
94
- # @note Currently we do nothing with "alternative", "inverted full name", "pseudonym", and "transliteration" types.
95
- def name_values
96
- Utils.flatten_nested_values(cocina).each_with_object({}) do |node, hash|
97
- type = node["type"] || "name"
98
- hash[type] ||= []
99
- hash[type] << node["value"]
100
- end.compact_blank
12
+ # The class to use for parallel values.
13
+ # @return [Class]
14
+ def parallel_value_class
15
+ NameValue
101
16
  end
102
17
  end
103
18
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CocinaDisplay
4
+ module Contributors
5
+ # A name associated with a contributor in a single language/script.
6
+ class NameValue < Parallel::ParallelValue
7
+ # The display string for the name, optionally including life dates.
8
+ # @param with_date [Boolean] Include life dates, if present
9
+ # @return [String]
10
+ # @example no dates
11
+ # name.to_s # => "King, Martin Luther, Jr."
12
+ # @example with dates
13
+ # name.to_s(with_date: true) # => "King, Martin Luther, Jr., 1929-1968"
14
+ def to_s(with_date: false)
15
+ if cocina["value"].present?
16
+ cocina["value"]
17
+ elsif dates_str.present? && with_date
18
+ Utils.compact_and_join([full_name_str, dates_str], delimiter: ", ")
19
+ else
20
+ full_name_str.presence
21
+ end
22
+ end
23
+
24
+ # The full name as a string, combining all name components and terms of address.
25
+ # @return [String]
26
+ def full_name_str
27
+ Utils.compact_and_join(name_components.push(terms_of_address_str), delimiter: ", ")
28
+ end
29
+
30
+ # List of all name components.
31
+ # If any of forename, surname, or term of address are present, those are used.
32
+ # Otherwise, fall back to any names explicitly marked as "name" or untyped.
33
+ # @return [Array<String>]
34
+ def name_components
35
+ [surname_str, forename_str].compact_blank.presence || Array(name_values["name"])
36
+ end
37
+
38
+ # Flatten all terms of address into a comma-delimited string.
39
+ # @return [String]
40
+ def terms_of_address_str
41
+ Utils.compact_and_join(Array(name_values["term of address"]), delimiter: ", ")
42
+ end
43
+
44
+ # Flatten all forename values and ordinals into a whitespace-delimited string.
45
+ # @return [String]
46
+ def forename_str
47
+ Utils.compact_and_join(Array(name_values["forename"]) + Array(name_values["ordinal"]), delimiter: " ")
48
+ end
49
+
50
+ # Flatten all surname values into a whitespace-delimited string.
51
+ # @return [String]
52
+ def surname_str
53
+ Utils.compact_and_join(Array(name_values["surname"]), delimiter: " ")
54
+ end
55
+
56
+ # Flatten all life and activity dates into a comma-delimited string.
57
+ # @return [String]
58
+ def dates_str
59
+ Utils.compact_and_join(Array(name_values["life dates"]) + Array(name_values["activity dates"]), delimiter: ", ")
60
+ end
61
+
62
+ private
63
+
64
+ # A hash mapping destructured name types to their values.
65
+ # Name values with no type are grouped under "name".
66
+ # @return [Hash<String, Array<String>>]
67
+ # @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#contributor-name-part-types-for-structured-value
68
+ # @note Currently we do nothing with "alternative", "inverted full name", "pseudonym", and "transliteration" types.
69
+ def name_values
70
+ Utils.flatten_nested_values(cocina).each_with_object({}) do |node, hash|
71
+ type = node["type"] || "name"
72
+ hash[type] ||= []
73
+ hash[type] << node["value"]
74
+ end.compact_blank
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CocinaDisplay
4
+ module Languages
5
+ # A language associated with part or all of a Cocina object.
6
+ class Language
7
+ SEARCHWORKS_LANGUAGES_FILE_PATH = CocinaDisplay.root / "config" / "searchworks_languages.yml"
8
+
9
+ attr_reader :cocina
10
+
11
+ # A hash of language codes to language names recognized by Searchworks.
12
+ # @return [Hash{String => String}]
13
+ def self.searchworks_languages
14
+ @searchworks_languages ||= YAML.safe_load_file(SEARCHWORKS_LANGUAGES_FILE_PATH)
15
+ end
16
+
17
+ # Create a Language object from Cocina structured data.
18
+ # @param cocina [Hash]
19
+ def initialize(cocina)
20
+ @cocina = cocina
21
+ end
22
+
23
+ # The language name for display.
24
+ # @return [String, nil]
25
+ def to_s
26
+ cocina["value"] || decoded_value
27
+ end
28
+
29
+ # The language code, e.g. an ISO 639 code like "eng" or "spa".
30
+ # @return [String, nil]
31
+ def code
32
+ cocina["code"]
33
+ end
34
+
35
+ # The IETF tag describing this language and script, if available.
36
+ # @return [String, nil]
37
+ # @example "ara-Latn" for Arabic written in Latin script
38
+ # @see https://en.wikipedia.org/wiki/IETF_language_tag
39
+ def ietf_tag
40
+ return code unless transliterated?
41
+
42
+ "#{code}-#{script.code}"
43
+ end
44
+
45
+ # True if the value is transliterated, i.e. non-English written in Latin script.
46
+ # @return [Boolean]
47
+ def transliterated?
48
+ !english? && script&.latin?
49
+ end
50
+
51
+ # True if the language is English.
52
+ # @return [Boolean]
53
+ def english?
54
+ to_s == "English"
55
+ end
56
+
57
+ # Decoded name of the language based on the code, if present.
58
+ # @return [String, nil]
59
+ def decoded_value
60
+ Language.searchworks_languages[code] if searchworks_language?
61
+ end
62
+
63
+ # Display label for this field.
64
+ # @return [String]
65
+ def label
66
+ cocina["displayLabel"].presence || I18n.t("cocina_display.field_label.language")
67
+ end
68
+
69
+ # True if the language is recognized by Searchworks.
70
+ # @see CocinaDisplay::Language.searchworks_languages
71
+ # @return [Boolean]
72
+ def searchworks_language?
73
+ Language.searchworks_languages.value?(cocina["value"]) || Language.searchworks_languages.key?(code)
74
+ end
75
+
76
+ # The script of the language, if specified.
77
+ # @return [CocinaDisplay::Languages::Script, nil]
78
+ def script
79
+ CocinaDisplay::Languages::Script.new(script_cocina) if script_cocina.present?
80
+ end
81
+
82
+ private
83
+
84
+ # The script might be in "script" (top-level) or "valueScript" (nested).
85
+ # @return [Hash, nil]
86
+ def script_cocina
87
+ cocina["valueScript"] || cocina["script"]
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,24 @@
1
+ module CocinaDisplay
2
+ module Languages
3
+ # A script used to write a Language.
4
+ class Script
5
+ attr_reader :cocina
6
+
7
+ def initialize(cocina)
8
+ @cocina = cocina
9
+ end
10
+
11
+ # The script code, e.g. an ISO 15924 code like "Latn" or "Cyrl".
12
+ # @return [String, nil]
13
+ def code
14
+ cocina["code"]
15
+ end
16
+
17
+ # True if the script is Latin.
18
+ # @return [Boolean]
19
+ def latin?
20
+ code == "Latn"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ module CocinaDisplay
2
+ module Notes
3
+ # A note associated with a cocina record
4
+ class Note < Parallel::Parallel
5
+ # Use the main parallel value to identify the type of note.
6
+ delegate :abstract?, :general_note?, :preferred_citation?, :table_of_contents?, :part?, to: :main_value
7
+
8
+ # Display methods reference the main note value. For parallel values,
9
+ # see #translated_value and #transliterated_value.
10
+ delegate :to_s, :values, :values_by_type, to: :main_value
11
+
12
+ # Label used to render the note for display.
13
+ # Uses a displayLabel if available, otherwise tries to look up via type.
14
+ # Falls back to a default label derived from the type or a generic note label if
15
+ # no type is set.
16
+ # @return [String]
17
+ def label
18
+ display_label ||
19
+ I18n.t(type&.parameterize&.underscore, default: default_label, scope: "cocina_display.field_label.note")
20
+ end
21
+
22
+ private
23
+
24
+ # The class to use for parallel values.
25
+ # @return [Class]
26
+ def parallel_value_class
27
+ NoteValue
28
+ end
29
+
30
+ # The default label for the note if no i18n key exists.
31
+ # @return [String]
32
+ def default_label
33
+ type&.capitalize || I18n.t("cocina_display.field_label.note.note")
34
+ end
35
+ end
36
+ end
37
+ end