foodsafety

package module
v1.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: MIT Imports: 8 Imported by: 0

README

food-safety (Go)

Open dataset of restaurant menu allergens and declarations — 28 allergen keys from EU Reg. 1169/2011 Annex II, 22 declarations, footnote codes, 15 icons, and six languages.

Semantic keys instead of country-specific numbers. Store the key, render the code — never the other way round.

No dependencies. The dataset is embedded with //go:embed, so your binary is self-contained and there is nothing to find on disk at runtime.

go get github.com/menuella/food-safety/packages/go@latest
import foodsafety "github.com/menuella/food-safety/packages/go"

Use it

de, err := foodsafety.GetDisclosures("de")
if err != nil {
    return err
}

for _, a := range de.Allergens {
    if a.Key == "WHEAT" {
        fmt.Println(a.Name)        // Weizen
        fmt.Println(a.Declaration) // Enthält Getreide und glutenhaltige Erzeugnisse
    }
}

Hand it the locale your application already resolved. This package does no i18n: an unknown locale returns an error wrapping ErrUnsupportedLocale, because a silently wrong language on an allergen panel is worse than a loud failure.

if errors.Is(err, foodsafety.ErrUnsupportedLocale) { … }

Keys, not codes

A product row stores WHEAT — not A6, not 21. Footnote codes are a rendering of the key, chosen at print time and varying by region and template:

codes, _ := foodsafety.GetCodes()
codes.Allergens["WHEAT"]        // "A6"
codes.Declarations["SWEETENERS"] // "12"

CEREALS and TREE_NUTS are groups, not keys: 14 groups, only 12 selectable. The law requires naming the specific grain or nut, so you store WHEAT and render the group declaration above it.

Icons

15 glyphs, named after the key rather than the depiction — sulphites, not wine — so a redraw never changes what a symbol means. Every shape paints with currentColor, so a glyph follows the surrounding text colour and a light or dark theme with no second asset.

icon, _ := foodsafety.GetIcon("cereals")
icon.ViewBox                    // "0 0 24 24"

svg, _ := foodsafety.IconToSVG("cereals", foodsafety.SVGOptions{Size: 16})

Attributes are emitted in sorted order, so the markup is byte-stable across runs — Go randomises map iteration on purpose, and unstable output breaks snapshot tests and any cache keyed on it.

An icon means "contains wheat". Render it alongside the declaration text, never instead of it. IconToSVG emits aria-hidden unless you pass a Title.

API

GetDisclosures(locale) every allergen and declaration for a locale
GetCodes() · CodeScheme() the footnote-code scheme
GetIcon(name) · IconToSVG(name, opts) glyphs as data or markup
Locales() the six supported locales
AllergenKeys() · DeclarationKeys() · IconNames() the vocabularies
IsLocale · IsAllergenKey · IsDeclarationKey guards for untrusted input
LoadDataset(name) the raw embedded JSON, for tooling
ErrUnsupportedLocale · ErrUnknownIcon sentinels for errors.Is

Versioning

This is a nested module, so its releases carry the directory prefix:

packages/go/v1.3.1

A workflow derives that tag from the repository's plain v1.3.1 on push, so both always point at the same commit.

Same data, other ecosystems

Go this module
npm @menuella/food-safety
NuGet Menuella.FoodSafety
pub.dev menuella_food_safety
PyPI menuella-food-safety
Packagist menuella/food-safety
Swift MenuellaFoodSafety
Maven Central com.menuella:food-safety
crates.io menuella-food-safety
RubyGems menuella-food_safety

The dataset encodes the allergen groups of EU Reg. 1169/2011 Annex II and declarations commonly required alongside them. Which disclosures a business must make, and how, is a matter for that business and its jurisdiction.

License

MIT.

Documentation

Overview

Package foodsafety is 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, err := foodsafety.GetDisclosures("de")
// allergen "WHEAT" → "Enthält Getreide und glutenhaltige Erzeugnisse"

This package does not do i18n. Hand it the locale your application already resolved; it will not sniff the environment, negotiate, or quietly fall back.

The dataset is embedded at compile time, so the binary is self-contained and there is nothing to find on disk at runtime. encoding/json is in the standard library, so the module has no dependencies.

Index

Constants

View Source
const FallbackLocale = "en"

FallbackLocale is the locale a bundle falls back to for anything it does not itself carry.

Variables

View Source
var (
	// ErrUnsupportedLocale means the locale has no bundle. Locales lists those
	// that do.
	ErrUnsupportedLocale = errors.New("foodsafety: unsupported locale")

	// ErrUnknownIcon means the name has no glyph.
	ErrUnknownIcon = errors.New("foodsafety: unknown icon")
)

Sentinel errors, so callers can branch with errors.Is rather than matching on message text.

Functions

func AllergenKeys

func AllergenKeys() ([]string, error)

AllergenKeys returns every selectable allergen key, in canonical order.

Keys are locale-independent, so this reads the structural file rather than taking a locale.

func CodeScheme

func CodeScheme() (string, error)

CodeScheme returns the scheme these codes belong to.

func DeclarationKeys

func DeclarationKeys() ([]string, error)

DeclarationKeys returns every declaration key, in canonical order.

func IconNames

func IconNames() ([]string, error)

IconNames returns every icon name that has a glyph.

func IconToSVG

func IconToSVG(name string, opts SVGOptions) (string, error)

IconToSVG returns the glyph as an <svg> string, for anything that interpolates markup — templates, e-mail, PDF.

It returns an error wrapping ErrUnknownIcon if the name has no glyph.

func IsAllergenKey

func IsAllergenKey(value string) bool

IsAllergenKey reports whether value is a current allergen key. Retired keys return false.

func IsDeclarationKey

func IsDeclarationKey(value string) bool

IsDeclarationKey reports whether value is a current declaration key.

func IsLocale

func IsLocale(value string) bool

IsLocale reports whether value is a locale with a bundle.

func LoadDataset

func LoadDataset(name string) ([]byte, error)

LoadDataset returns the raw JSON for one of the packaged files, for tooling that wants the dataset rather than resolved values.

Valid names are "allergens.json", "declarations.json", "codes.json" and "icons.json".

func Locales

func Locales() []string

Locales returns the locales with a prebuilt bundle.

A fresh slice each call, so a caller cannot reorder the package's own state.

Types

type Allergen

type Allergen struct {
	// Key is the stored identifier, e.g. "WHEAT". This is what a product row
	// holds.
	Key string `json:"key"`

	// Group is its LMIV group, e.g. "CEREALS". Twelve groups are also
	// selectable keys; CEREALS and TREE_NUTS are display-only, because the law
	// requires naming the specific grain or nut.
	Group string `json:"group"`

	// IsMember is true when this key is one member of a multi-member group.
	IsMember bool `json:"isMember"`

	// Icon is the glyph name, e.g. "cereals".
	Icon string `json:"icon"`

	// Name is the short label, e.g. "Wheat".
	Name string `json:"name"`

	// Declaration is the sentence with legal force. This is what must reach
	// the guest.
	Declaration string `json:"declaration"`

	// Description is a longer explanation, for tooltips and help text.
	Description string `json:"description"`
}

Allergen is one allergen, resolved for a locale.

type Codes

type Codes struct {
	Scheme     string `json:"scheme"`
	Convention string `json:"convention"`

	// Allergens maps an allergen key to its printable code, e.g. WHEAT → A6.
	Allergens map[string]string `json:"allergens"`

	// Declarations maps a declaration key to its code, e.g. SWEETENERS → 12.
	Declarations map[string]string `json:"declarations"`
}

Codes is the footnote-code scheme: letters for allergens, numbers for declarations.

func GetCodes

func GetCodes() (Codes, error)

GetCodes returns the footnote-code scheme.

type Declaration

type Declaration struct {
	Key string `json:"key"`

	// Category is one of ADDITIVE, BEVERAGE, WARNING, PRODUCT.
	Category string `json:"category"`

	// Icon is the glyph name. All declarations share one generic glyph.
	Icon string `json:"icon"`

	Name        string `json:"name"`
	Description string `json:"description"`
}

Declaration is one additive, beverage or product note, resolved for a locale.

type Disclosures

type Disclosures struct {
	Locale string `json:"locale"`

	// FallbackLocale is the locale this bundle falls back to for anything it
	// does not itself carry.
	FallbackLocale string `json:"fallbackLocale"`

	Allergens    []Allergen    `json:"allergens"`
	Declarations []Declaration `json:"declarations"`
}

Disclosures is every disclosure for one locale, ready to render.

func GetDisclosures

func GetDisclosures(locale string) (Disclosures, error)

GetDisclosures returns every disclosure for locale, ready to render.

It returns an error wrapping ErrUnsupportedLocale 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 IsLocale is there to ask.

type Icon

type Icon struct {
	// ViewBox is always "0 0 24 24".
	ViewBox string
	Nodes   []IconNode
}

Icon is a glyph as data, for callers that build shapes rather than markup.

func GetIcon

func GetIcon(name string) (Icon, error)

GetIcon returns 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.

It returns an error wrapping ErrUnknownIcon if the name has no glyph.

type IconNode

type IconNode struct {
	// Tag is "path" or "circle".
	Tag string

	// Attributes are named in SVG spelling (fill-rule, not fillRule).
	Attributes map[string]string
}

IconNode is a single shape in a glyph.

type SVGOptions

type SVGOptions struct {
	// Size is the rendered width and height in px. Zero means 24.
	Size int

	// Class is added to the root element.
	Class string

	// Title gives the glyph an accessible name and role="img".
	//
	// Leave it empty when the declaration text sits beside the icon: then the
	// glyph is decoration, stays aria-hidden, and the text carries the meaning.
	// That is the correct default on a legal surface.
	Title string
}

SVGOptions tunes IconToSVG.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL