npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@menuella/food-safety

v2.1.0

Published

Open dataset of restaurant menu disclosures — the 14 EU allergen groups and Menuella Declarations, in 50 languages, keyed by stable semantic keys.

Readme

@menuella/food-safety

Open dataset of restaurant menu disclosures — the 14 EU allergen groups and Menuella Declarations, with codes, icons and labels in 50 languages, keyed by stable semantic keys.

Verify npm NuGet License: MIT

Store the key. Render the code. Never the reverse.

Install

One dataset, one version, released from one tag.

| | | |---|---| | npm | npm i @menuella/food-safety | | NuGet | dotnet add package Menuella.FoodSafety | | Dart | dart pub add menuella_food_safety | | Python | pip install menuella-food-safety | | PHP | composer require menuella/food-safety | | Rust | cargo add menuella-food-safety | | Ruby | bundle add menuella-food_safety | | Go | go get github.com/menuella/food-safety/packages/go/v2 | | Swift | .package(url: "https://github.com/menuella/food-safety", from: "2.1.0") | | Gradle | implementation("com.menuella:food-safety:2.1.0") |

import de from "@menuella/food-safety/locales/de"
import { resolveDisclosures } from "@menuella/food-safety"

const { allergens } = resolveDisclosures(de, ["WHEAT", "BARLEY"])
allergens[0].declaration                 // "Enthält Getreide und glutenhaltige Erzeugnisse"
allergens[0].members.map((m) => m.name)  // ["Gerste", "Weizen"]

Import only the locales you render. The root entry point carries the vocabulary and no locale data at all, so a bundle pays for exactly the languages it uses:

| Entry point | What it gives you | Locale data in your bundle | |---|---|---| | @menuella/food-safety | keys, groups, guards, types, resolveDisclosures | none | | @menuella/food-safety/locales/<tag> | one locale, typed and frozen | that locale only | | @menuella/food-safety/load | loadDisclosures(tag) — fetched on demand | one chunk per locale | | @menuella/food-safety/all | getDisclosures(tag) — synchronous | every locale |

Hand it the locale your app already resolved — this package does no i18n of its own. See docs/rendering.md for grouping, icons, codes and the type surface.

The Menuella standard

Five pieces, one vocabulary, every region:

| | | |---|---| | Menuella Allergen Keys | 28 semantic keys covering the 14 EU allergen groups | | Menuella Declarations | 22 keys for additives, beverage declarations, warnings and product indications | | Menuella Codes | the short codes printed in a menu legend — WHEAT → A6 | | Menuella Icons | 15 solid glyphs, one per group | | Menuella Translations | localized labels in 50 languages |

Menuella Declarations are a standardized restaurant menu disclosure vocabulary developed by Menuella for consistent rendering across applications and regions. They are built for portability, not to reproduce any country's official legal terminology — see docs/regions.md.

You never work with country-specific terminology. A menu in Vienna, Lyon or Dublin uses the same keys, so there is no per-country branching in your code and no per-country churn in this package. Legal mappings can be layered on internally later without any of these keys changing.


Keys, not codes

Every entry is identified by a stable semantic key — WHEAT, SESAME, NITRITE_CURING_SALT — and never by a number or letter.

Menus print codes like A6 for wheat or 11 for sweeteners. Those codes are not standardized between print shops, or even between states, so the same 11 means different things on different menus. They are a rendering convention, not an identity. Storing them is how disclosure data rots.

So this dataset stores the meaning as the identity, and ships the numbering separately as what it is — a rendering convention. One scheme, usable in any region:

import codes from '@menuella/food-safety/data/codes.json'

codes.allergens['WHEAT']      // → "A6"
codes.declarations['SWEETENERS'] // → "12"

The letters follow long-established EU menu practice so guests recognise them; the numbers are Menuella's own contiguous 1–22, because national legends are not standardized and there was nothing worth mirroring. You get numbers when you need to print a legend, without the numbers ever becoming an identity. A key is legible in a database dump, survives a change of print shop, and is what an AI agent or Google Business Profile actually understands.

Store keys. Render codes. Never the reverse. Full tables and the caveats in docs/regions.md.


What's included

| Module | Structural file | Items | Source | |---|---|---|---| | Allergens | data/allergens.json | 28 keys / 14 groups | EU Reg. 1169/2011 Annex II (LMIV) | | Menuella Declarations | data/declarations.json | 22 | Menuella standard |

Allergens — 28 keys, not 14

The law names 14 groups, but two of them may never be declared as a group. LMIV (and the Veterinäramt) require naming the specific cereal and the specific tree nut — "glutenhaltiges Getreide" and "Schalenfrüchte" are not lawful declarations on their own. So those two groups are expanded into their members and the group itself is not selectable:

  • CEREALS → RYE BARLEY EMMER EINKORN SPELT WHEAT OATS KHORASAN
  • TREE_NUTS → MACADAMIA ALMONDS BRAZIL_NUTS PECANS PISTACHIOS WALNUTS CASHEWS HAZELNUTS
  • The other 12 groups are one key each: CRUSTACEANS EGGS FISH PEANUTS SOY MILK CELERY MUSTARD SESAME SULPHITES LUPINS MOLLUSCS

group and isMember carry that structure, and declaration is the group sentence — render it once per group, with the specific members beneath:

Enthält Getreide und glutenhaltige Erzeugnisse — Weizen, Gerste

Menuella Declarations

Menuella Declarations are Menuella's standardized menu disclosure vocabulary and rendering system — a consistent set of declaration keys and display codes for restaurant menus across every supported region. They cover additive declarations plus beverage declarations, mandatory warnings and other product-specific indications; category tells them apart: ADDITIVE, BEVERAGE, WARNING, PRODUCT.

The set is designed for portability and consistency, and is not intended to represent any country's official legal terminology. One vocabulary in Vienna, Lyon and Dublin means no per-country branching in your code — and no per-country churn in this package.

The vocabulary was inspired by common German restaurant menu practice, which is unusually well developed, then generalized: packaging-only entries were dropped as unsuitable for a menu, and the numbering replaced. See docs/regions.md.

Branded end to end: the file is declarations.json, the type is DeclarationKey, the bundle field is declarations. "Additives" is the narrower, everyday word — these keys also cover beverage and product notes that are not additives at all.

Tag dish "Pizza Sucuk" with [sucuk, cheddar, weizenmehl] ↓ allergens = MILK (cheddar) + WHEAT (flour) = ["MILK", "WHEAT"] declarations = NITRITE_CURING_SALT (sucuk) = ["NITRITE_CURING_SALT"]


---

## Languages

| Tag | Language | Allergens | Declarations |
|---|---|---|---|
| `am` | Amharic | 28 ✅ + declarations | 22 ✅ |
| `ar` | Arabic (RTL) | 28 ✅ + declarations | 22 ✅ |
| `bg` | Bulgarian | 28 ✅ + declarations | 22 ✅ |
| `bn` | Bengali | 28 ✅ + declarations | 22 ✅ |
| `ca` | Catalan | 28 ✅ + declarations | 22 ✅ |
| `cs` | Czech | 28 ✅ + declarations | 22 ✅ |
| `da` | Danish | 28 ✅ + declarations | 22 ✅ |
| `de` | German | 28 ✅ + declarations | 22 ✅ |
| `el` | Greek | 28 ✅ + declarations | 22 ✅ |
| `en` | English | 28 ✅ + declarations | 22 ✅ |
| `es` | Spanish | 28 ✅ + declarations | 22 ✅ |
| `et` | Estonian | 28 ✅ + declarations | 22 ✅ |
| `fa` | Persian (RTL) | 28 ✅ + declarations | 22 ✅ |
| `fi` | Finnish | 28 ✅ + declarations | 22 ✅ |
| `fil` | Filipino | 28 ✅ + declarations | 22 ✅ |
| `fr` | French | 28 ✅ + declarations | 22 ✅ |
| `he` | Hebrew (RTL) | 28 ✅ + declarations | 22 ✅ |
| `hi` | Hindi | 28 ✅ + declarations | 22 ✅ |
| `hr` | Croatian | 28 ✅ + declarations | 22 ✅ |
| `hu` | Hungarian | 28 ✅ + declarations | 22 ✅ |
| `id` | Indonesian | 28 ✅ + declarations | 22 ✅ |
| `it` | Italian | 28 ✅ + declarations | 22 ✅ |
| `ja` | Japanese | 28 ✅ + declarations | 22 ✅ |
| `ka` | Georgian | 28 ✅ + declarations | 22 ✅ |
| `ko` | Korean | 28 ✅ + declarations | 22 ✅ |
| `ku` | Kurdish (Kurmanji) | 28 ✅ + declarations | 22 ✅ |
| `lt` | Lithuanian | 28 ✅ + declarations | 22 ✅ |
| `lv` | Latvian | 28 ✅ + declarations | 22 ✅ |
| `ms` | Malay | 28 ✅ + declarations | 22 ✅ |
| `nl` | Dutch | 28 ✅ + declarations | 22 ✅ |
| `no` | Norwegian (Bokmål) | 28 ✅ + declarations | 22 ✅ |
| `pl` | Polish | 28 ✅ + declarations | 22 ✅ |
| `pt-BR` | Portuguese (Brazil) | 28 ✅ + declarations | 22 ✅ |
| `pt-PT` | Portuguese (Portugal) | 28 ✅ + declarations | 22 ✅ |
| `ro` | Romanian | 28 ✅ + declarations | 22 ✅ |
| `ru` | Russian | 28 ✅ + declarations | 22 ✅ |
| `sk` | Slovak | 28 ✅ + declarations | 22 ✅ |
| `sl` | Slovenian | 28 ✅ + declarations | 22 ✅ |
| `sq` | Albanian | 28 ✅ + declarations | 22 ✅ |
| `sr-Latn` | Serbian (Latin) | 28 ✅ + declarations | 22 ✅ |
| `sv` | Swedish | 28 ✅ + declarations | 22 ✅ |
| `sw` | Swahili | 28 ✅ + declarations | 22 ✅ |
| `ta` | Tamil | 28 ✅ + declarations | 22 ✅ |
| `th` | Thai | 28 ✅ + declarations | 22 ✅ |
| `tr` | Turkish | 28 ✅ + declarations | 22 ✅ |
| `uk` | Ukrainian | 28 ✅ + declarations | 22 ✅ |
| `ur` | Urdu (RTL) | 28 ✅ + declarations | 22 ✅ |
| `vi` | Vietnamese | 28 ✅ + declarations | 22 ✅ |
| `zh-Hans` | Chinese (Simplified) | 28 ✅ + declarations | 22 ✅ |
| `zh-Hant` | Chinese (Traditional) | 28 ✅ + declarations | 22 ✅ |

Locales are [BCP 47](https://www.rfc-editor.org/info/bcp47) tags and are matched exactly. Where a language has variants that word allergens differently, each variant ships under its own tag and there is no bare one: `pt-BR` says *gergelim* where `pt-PT` says *sésamo*, and `zh-Hant` is written in a different script from `zh-Hans`. An app that holds only `pt` or `zh` decides which variant it means. A bare tag means one stated form of its language: `ku` is Kurmanji.

Every language is complete: all 28 allergens and 22 declarations carry a name, description and — for allergens — the group declaration sentence. Arabic, Hebrew, Persian and Urdu are right-to-left; the data itself is plain UTF-8 with no embedded direction control characters, so consumers wrap the rendered block in a container with `dir="rtl"` (or the framework equivalent) exactly as they already do for any other RTL text in the same UI.

Missing a language you need? **Open a PR.** Adding one = one new file under `data/translations/<module>/<tag>.json`.

---

## What's NOT in here (by design)

- ❌ Codes as identifiers — they ship in `data/codes/` for rendering, but are never a key
- ❌ **Ingredient → key mappings.** The keys are designed so a dish's disclosures *can* be rolled up from its ingredients, but the mapping itself is curation, not vocabulary — it lives in [Menuella](https://www.menuella.com), or you build your own.
- ❌ Preference, lifestyle and taste vocabularies (vegan, halal, spiciness) — real menu attributes, but not *safety* disclosures
- ❌ Nutrition values and country-of-origin — a different obligation with a different shape; out of scope here
- ❌ Industrial / packaging labeling rules (this is *menu* scope only)
- ❌ Manufacturer-specific labeling
- ❌ Runtime code, parsers, or servers — companion repos planned:
  - `menuella/food-safety-js` — TS/JS helpers (formatters, validators, rollup)
  - `menuella/food-safety-detect` — AI-assisted detection from product name + description

---

## Versioning

Conservative SemVer:

- **MAJOR** — breaking schema change (avoided where possible)
- **MINOR** — new language, new module, new entries
- **PATCH** — translation fixes, typo fixes, description clarifications

Keys (`WHEAT`, `NITRITE_CURING_SALT`) and `canonicalKey` values are **stable identifiers** — they will never be renamed or reused. That guarantee is exactly why they replaced the codes.

Pin to a major version in production:

```jsonc
{ "dependencies": { "@menuella/food-safety": "^2" } }

See CHANGELOG.md.


Documentation

| | | |---|---| | docs/rendering.md | turning keys into what a guest reads — grouping, icons, codes, types | | docs/data-shapes.md | the on-disk format, for editing the dataset or writing a binding | | docs/legal-references.md | the source regulations behind every entry | | docs/regions.md | how the codes relate to regional conventions | | CONTRIBUTING.md | proposing a change, and validating one |

Legal disclaimer

This dataset is provided for informational purposes. We cite source regulations in docs/legal-references.md and aim for accuracy, but you remain responsible for compliance with the laws of your jurisdiction. This dataset is not legal advice.

Every locale translates the same EU Annex II vocabulary. A translation is not a statement of another jurisdiction's rules: pt-BR, for example, words the EU allergens in Brazilian Portuguese, and is not a rendering of Brazil's own allergen labelling regulation, whose list differs.


Contributing

Translations, corrections and new languages are welcome.

Anything touching allergens or legal wording needs a citation. People with allergies read what we publish.


License

MIT © Menuella

Use it, fork it, embed it in commercial products. MIT asks one thing in return: that the copyright and permission notice travels with substantial portions of the dataset.

  • Installing from npm — nothing to do. LICENSE ships inside the package.
  • Vendoring files into your own repo — copy LICENSE alongside them, or credit @menuella/food-safety in your own notices.
  • Inlining the icons — SVG comments get stripped by optimizers, so there is no per-file banner to preserve. The root LICENSE covers them.

Links