Module: Menuella::FoodSafety
- Defined in:
- lib/menuella/food_safety.rb,
lib/menuella/food_safety/version.rb
Overview
The Menuella food-safety vocabulary: EU Reg. 1169/2011 Annex II allergens and the Menuella declarations, in six languages.
Semantic keys instead of country-specific numbers — store the key, render the code, never the reverse.
de = Menuella::FoodSafety.disclosures("de")
de.allergens.find { _1.key == "WHEAT" }.declaration
# => "Enthält Getreide und glutenhaltige Erzeugnisse"
This module does no i18n. Hand it the locale your application already resolved; it will not sniff the environment, negotiate, or quietly fall back.
Everything it returns is frozen. The dataset is a shared constant, and a caller that could mutate one bundle would be mutating every other caller's copy in the same process.
Defined Under Namespace
Classes: Allergen, Codes, Declaration, Disclosures, Error, Icon, IconNode, UnknownIconError, UnsupportedLocaleError
Constant Summary collapse
- VERSION =
Kept in step with every other binding by scripts/verify.mjs: one dataset, one version, released from one tag.
"1.3.0"
Class Method Summary collapse
-
.allergen_key?(value) ⇒ Boolean
True when
valueis a current allergen key. -
.allergen_keys ⇒ Object
Every selectable allergen key, in canonical order.
-
.code_scheme ⇒ Object
The scheme #codes belong to.
-
.codes ⇒ Object
The footnote-code scheme.
-
.dataset(name) ⇒ Object
The raw packaged JSON for
name, for tooling that wants the dataset rather than the objects. -
.declaration_key?(value) ⇒ Boolean
True when
valueis a current declaration key. -
.declaration_keys ⇒ Object
Every declaration key, in canonical order.
-
.disclosures(locale) ⇒ Object
Every disclosure for
locale, ready to render. -
.fallback_locale ⇒ Object
The locale a bundle falls back to for anything it does not itself carry.
-
.icon(name) ⇒ Object
The glyph named
name, as data. -
.icon_names ⇒ Object
Every icon name that has a glyph, sorted.
-
.icon_to_svg(name, size: 24, css_class: nil, title: nil) ⇒ Object
The glyph named
nameas an string, for anything that interpolates markup — server-rendered HTML, e-mail, PDF. -
.locale?(value) ⇒ Boolean
True when
valueis a locale with a bundle. -
.locales ⇒ Object
Locales with a prebuilt bundle.
Class Method Details
.allergen_key?(value) ⇒ Boolean
True when value is a current allergen key. Retired keys and
display-only groups return false.
196 197 198 |
# File 'lib/menuella/food_safety.rb', line 196 def allergen_key?(value) allergen_keys.include?(value) end |
.allergen_keys ⇒ Object
Every selectable allergen key, in canonical order.
Derived from the dataset, never hand-listed. Keys are locale-independent, so this reads the fallback bundle rather than taking a locale argument.
175 176 177 |
# File 'lib/menuella/food_safety.rb', line 175 def allergen_keys memo(:allergen_keys) { disclosures(fallback_locale).allergens.map(&:key).freeze } end |
.code_scheme ⇒ Object
The scheme #codes belong to.
166 167 168 |
# File 'lib/menuella/food_safety.rb', line 166 def code_scheme codes.scheme end |
.codes ⇒ Object
The footnote-code scheme.
154 155 156 157 158 159 160 161 162 163 |
# File 'lib/menuella/food_safety.rb', line 154 def codes memo(:codes) do raw = read("codes") Codes.new( scheme: raw.fetch("scheme").freeze, allergens: deep_freeze(raw.fetch("allergens")), declarations: deep_freeze(raw.fetch("declarations")), ) end end |
.dataset(name) ⇒ Object
The raw packaged JSON for name, for tooling that wants the dataset
rather than the objects. Returns a fresh string each call.
246 247 248 |
# File 'lib/menuella/food_safety.rb', line 246 def dataset(name) File.read(File.join(DATA_DIR, "#{name.sub(/\.json\z/, '')}.json"), encoding: "UTF-8") end |
.declaration_key?(value) ⇒ Boolean
True when value is a current declaration key.
201 202 203 |
# File 'lib/menuella/food_safety.rb', line 201 def declaration_key?(value) declaration_keys.include?(value) end |
.declaration_keys ⇒ Object
Every declaration key, in canonical order.
180 181 182 |
# File 'lib/menuella/food_safety.rb', line 180 def declaration_keys memo(:declaration_keys) { disclosures(fallback_locale).declarations.map(&:key).freeze } end |
.disclosures(locale) ⇒ Object
Every disclosure for locale, ready to render.
Raises UnsupportedLocaleError rather than falling back: a silently wrong language on an allergen panel is worse than a loud failure. The caller knows which locales it supports, and #locale? is there to ask.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/menuella/food_safety.rb', line 121 def disclosures(locale) raise UnsupportedLocaleError, locale unless locale?(locale) memo(:bundles, locale) do raw = read("bundles/#{locale}") Disclosures.new( locale: raw.fetch("locale").freeze, fallback_locale: raw.fetch("fallbackLocale").freeze, allergens: raw.fetch("allergens").map { |a| Allergen.new( key: a.fetch("key").freeze, group: a.fetch("group").freeze, is_member: a.fetch("isMember"), icon: a.fetch("icon").freeze, name: a.fetch("name").freeze, declaration: a.fetch("declaration").freeze, description: a.fetch("description").freeze, ) }.freeze, declarations: raw.fetch("declarations").map { |d| Declaration.new( key: d.fetch("key").freeze, category: d.fetch("category").freeze, icon: d.fetch("icon").freeze, name: d.fetch("name").freeze, description: d.fetch("description").freeze, ) }.freeze, ) end end |
.fallback_locale ⇒ Object
The locale a bundle falls back to for anything it does not itself carry.
112 113 114 |
# File 'lib/menuella/food_safety.rb', line 112 def fallback_locale "en" end |
.icon(name) ⇒ Object
The glyph named name, as data.
Every shape paints with currentColor, so a glyph inherits the
surrounding text colour and follows a light or dark theme with no
second asset.
210 211 212 |
# File 'lib/menuella/food_safety.rb', line 210 def icon(name) icons.fetch(name) { raise UnknownIconError, name } end |
.icon_names ⇒ Object
Every icon name that has a glyph, sorted.
185 186 187 |
# File 'lib/menuella/food_safety.rb', line 185 def icon_names memo(:icon_names) { icons.keys.sort.freeze } end |
.icon_to_svg(name, size: 24, css_class: nil, title: nil) ⇒ Object
The glyph named name as an string, for anything that
interpolates markup — server-rendered HTML, e-mail, PDF.
Decorative by default: emits aria-hidden unless title: is given,
which switches it to role="img" with a
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/menuella/food_safety.rb', line 221 def icon_to_svg(name, size: 24, css_class: nil, title: nil) glyph = icon(name) out = +%(<svg xmlns="http://www.w3.org/2000/svg" viewBox="#{escape(glyph.view_box)}") out << %( width="#{size.to_i}" height="#{size.to_i}" fill="none") out << %( class="#{escape(css_class)}") if css_class && !css_class.empty? out << if title && !title.empty? %( role="img"><title>#{escape(title)}</title>) else %( aria-hidden="true" focusable="false">) end glyph.nodes.each do |node| out << "<#{node.tag}" node.attributes.each { |key, value| out << %( #{key}="#{escape(value)}") } out << "/>" end out << "</svg>" out.freeze end |
.locale?(value) ⇒ Boolean
True when value is a locale with a bundle.
190 191 192 |
# File 'lib/menuella/food_safety.rb', line 190 def locale?(value) locales.include?(value) end |
.locales ⇒ Object
Locales with a prebuilt bundle.
107 108 109 |
# File 'lib/menuella/food_safety.rb', line 107 def locales @locales ||= %w[de en es fr it tr].freeze end |