Skip to content

Item Catalog — i18n Keys

Proposed keys for src/locales/en/item-catalog.json and src/locales/pl/item-catalog.json. Polish translations are starting drafts — refine during implementation against actual UI screens. English is the development source.

Namespace: item-catalog (kebab-case, matches FE folder + URL stem). Loaded by i18next when the first catalog route is visited. Always-loaded common namespace owns the sidebar label (common:nav.catalog, common:nav.items, common:nav.groups) — those are added to the common locale files, not here, when M0 ticket 12 lands the NavItems.

Conventions

  • Status enums translate by valuet('item-catalog:itemType.${item.item_type}'). Keys mirror BE enum literals (lower snake_case for itemType, lower for storageUnit and status).
  • Error messages from the BE error envelope are NOT translated here. Field-level errors come from the validation namespace via zod-i18n-map; route-level errors are surfaced as toasts whose copy is owned by the shared handleApiError parser. Only the four item-deactivation guard codes get module-local copy below because they're rendered as inline alerts inside the deactivate ConfirmDialog (route-level toast doesn't fit the UX).
  • Semantic buttons own their own translation (<SaveButton /> renders "Zapisz" / "Save" from common). Never pass button labels from page-level keys.
  • Pluralisation uses i18next's plural rules (_one, _other, etc.). Polish uses three forms (_one, _few, _other) per CLDR — covered explicitly for itemCount and the import success count.

Proposed tree

{
  "page": {
    "itemList": {
      "title":         { "pl": "Katalog pozycji",        "en": "Item catalog" },
      "description":   { "pl": "Wszystkie surowce, półprodukty, produkty i opakowania.", "en": "All ingredients, intermediates, products and packaging." },
      "createButton":  { "pl": "Nowa pozycja",           "en": "New item" },
      "importButton":  { "pl": "Importuj",               "en": "Import" },
      "filter": {
        "searchPlaceholder": { "pl": "Szukaj po nazwie, kodzie, opisie, tagach…", "en": "Search by name, code, description, tags…" },
        "type":              { "pl": "Typ",                "en": "Type" },
        "status":            { "pl": "Status",             "en": "Status" },
        "group":             { "pl": "Grupa",              "en": "Group" },
        "groupAll":          { "pl": "Wszystkie grupy",    "en": "All groups" },
        "groupPickTypeFirst":{ "pl": "Najpierw wybierz typ", "en": "Pick a type first" },
        "reset":             { "pl": "Resetuj filtry",     "en": "Reset filters" }
      },
      "stats": {
        "total":        { "pl": "Łącznie",                "en": "Total" },
        "active":       { "pl": "Aktywne",                "en": "Active" },
        "archived":     { "pl": "Zarchiwizowane",         "en": "Archived" },
        "byType":       { "pl": "Według typu",            "en": "By type" }
      },
      "column": {
        "code":         { "pl": "Kod",                    "en": "Code" },
        "name":         { "pl": "Nazwa",                  "en": "Name" },
        "type":         { "pl": "Typ",                    "en": "Type" },
        "group":        { "pl": "Grupa",                  "en": "Group" },
        "unit":         { "pl": "Jednostka",              "en": "Unit" },
        "status":       { "pl": "Status",                 "en": "Status" }
      },
      "emptyAll":       { "pl": "Brak pozycji w katalogu", "en": "No items in the catalog yet" },
      "emptyAllCta":    { "pl": "Dodaj pierwszą pozycję", "en": "Add the first item" },
      "emptyFiltered":  { "pl": "Brak pozycji pasujących do wybranych filtrów", "en": "No items match the current filters" },
      "emptyFilteredCta": { "pl": "Wyczyść filtry",       "en": "Clear filters" }
    },

    "itemDetail": {
      "backToList":     { "pl": "Wróć do katalogu",       "en": "Back to catalog" },
      "editButton":     { "pl": "Edytuj",                 "en": "Edit" },
      "deactivateButton": { "pl": "Dezaktywuj",           "en": "Deactivate" },
      "reactivateButton": { "pl": "Aktywuj ponownie",     "en": "Reactivate" },
      "systemBadge":    { "pl": "Pozycja systemowa",      "en": "System item" },
      "section": {
        "information":  { "pl": "Informacje o pozycji",   "en": "Item information" },
        "inventory":    { "pl": "Parametry magazynowe",   "en": "Inventory parameters" },
        "descriptionTags": { "pl": "Opis i tagi",         "en": "Description & tags" },
        "audit":        { "pl": "Audyt",                  "en": "Audit" },
        "activity":     { "pl": "Aktywność",              "en": "Activity" }
      },
      "audit": {
        "createdBy":    { "pl": "Utworzona przez",        "en": "Created by" },
        "createdAt":    { "pl": "Utworzona",              "en": "Created" },
        "updatedBy":    { "pl": "Zaktualizowana przez",   "en": "Updated by" },
        "updatedAt":    { "pl": "Zaktualizowana",         "en": "Updated" },
        "never":        { "pl": "Nigdy",                  "en": "Never" }
      },
      "activityPlaceholder": { "pl": "Pełna historia zdarzeń pojawi się gdy backend uruchomi dziennik audytu.", "en": "Full event history will appear once the backend audit log is wired up." },
      "deactivateConfirmTitle": { "pl": "Dezaktywować pozycję?", "en": "Deactivate this item?" },
      "deactivateConfirmBody":  { "pl": "Pozycja zostanie ukryta w nowych formularzach. Kod pozostaje zarezerwowany i nie będzie można go użyć dla innej pozycji.", "en": "The item will be hidden from new forms. Its code stays reserved and cannot be reused by another item." },
      "deactivateConfirmCta":   { "pl": "Dezaktywuj",     "en": "Deactivate" },
      "reactivateConfirmTitle": { "pl": "Aktywować pozycję ponownie?", "en": "Reactivate this item?" },
      "reactivateConfirmBody":  { "pl": "Pozycja będzie ponownie dostępna w wyszukiwarce i formularzach.", "en": "The item will be available again in search and forms." },
      "reactivateConfirmCta":   { "pl": "Aktywuj",        "en": "Reactivate" }
    },

    "itemCreate": {
      "title":          { "pl": "Nowa pozycja",           "en": "New item" },
      "description":    { "pl": "Dodaj pozycję do katalogu.", "en": "Add an item to the catalog." },
      "backToList":     { "pl": "Wróć do katalogu",       "en": "Back to catalog" },
      "fieldset": {
        "identity":     { "pl": "Identyfikacja",          "en": "Identity" },
        "classification": { "pl": "Klasyfikacja",         "en": "Classification" },
        "inventory":    { "pl": "Parametry magazynowe",   "en": "Inventory parameters" },
        "descriptionTags": { "pl": "Opis i tagi",         "en": "Description & tags" }
      },
      "helperImmutable": { "pl": "Kod, typ i jednostkę nadaje się raz – nie można ich później zmienić.", "en": "Code, type and unit are set once — they cannot be changed later." }
    },

    "itemEdit": {
      "title":          { "pl": "Edytuj pozycję",         "en": "Edit item" },
      "description":    { "pl": "Pola zablokowane to identyfikatory pozycji – ich zmiana zerwałaby spójność BOM-ów, stanów i faktur.", "en": "Locked fields are item identifiers — changing them would break BOM, stock and invoicing integrity." },
      "lockedHint":     { "pl": "Pola zablokowane",       "en": "Locked fields" },
      "lastUpdated":    { "pl": "Ostatnia zmiana: {{datetime}} przez {{user}}", "en": "Last updated {{datetime}} by {{user}}" },
      "conflict": {
        "title":        { "pl": "Pozycja zmieniła się w międzyczasie", "en": "This item changed in the meantime" },
        "body":         { "pl": "Inny użytkownik zapisał zmiany zanim wysłano formularz. Załaduj najnowszą wersję, aby kontynuować edycję.", "en": "Another user saved changes before this form was submitted. Reload the latest version to continue editing." },
        "reloadCta":    { "pl": "Załaduj najnowszą wersję", "en": "Reload latest" }
      },
      "partialSave": {
        "groupSectionError": { "pl": "Nie udało się zapisać przypisania do grupy.", "en": "Could not save the group assignment." }
      }
    },

    "itemImport": {
      "title":          { "pl": "Import pozycji z Excela", "en": "Import items from Excel" },
      "description":    { "pl": "Wgranie pliku wykonuje import w trybie wszystko-albo-nic. Każdy błąd w wierszu wstrzymuje zapis całej paczki.", "en": "Uploading the file runs an all-or-nothing import. A single row error halts the entire batch." },
      "backToList":     { "pl": "Wróć do katalogu",       "en": "Back to catalog" },
      "step": {
        "download":     { "pl": "Pobierz szablon",        "en": "Download template" },
        "upload":       { "pl": "Wgraj plik",             "en": "Upload file" },
        "review":       { "pl": "Sprawdź",                "en": "Review" },
        "confirm":      { "pl": "Gotowe",                 "en": "Done" }
      },
      "download": {
        "cardTitle":    { "pl": "Szablon importu",        "en": "Import template" },
        "cardDescription": { "pl": "Pusty arkusz .xlsx z poprawnymi nagłówkami kolumn.", "en": "Blank .xlsx with the correct column headers." },
        "cardButton":   { "pl": "Pobierz szablon",        "en": "Download template" }
      },
      "upload": {
        "dropzoneTitle": { "pl": "Przeciągnij plik .xlsx lub kliknij, aby wybrać", "en": "Drag your .xlsx file here, or click to choose" },
        "constraints":  { "pl": "Maks. 5 MB, maks. 500 wierszy.", "en": "Up to 5 MB, up to 500 rows." }
      },
      "review": {
        "loading":      { "pl": "Trwa walidacja wgranego pliku…", "en": "Validating the uploaded file…" },
        "success_one":  { "pl": "Plik jest gotowy do importu ({{count}} pozycja).", "en": "File is ready to import ({{count}} item)." },
        "success_few":  { "pl": "Plik jest gotowy do importu ({{count}} pozycje).", "en": "" },
        "success_other":{ "pl": "Plik jest gotowy do importu ({{count}} pozycji).", "en": "File is ready to import ({{count}} items)." },
        "errorsTitle":  { "pl": "Znaleziono błędy w wierszach", "en": "Errors found in rows" },
        "errorsBody":   { "pl": "Popraw plik i wgraj ponownie. Import jest wszystko-albo-nic.", "en": "Fix the file and upload again. Import is all-or-nothing." }
      },
      "confirm": {
        "title":        { "pl": "Import zakończony",      "en": "Import complete" },
        "body_one":     { "pl": "Dodano {{count}} pozycję do katalogu.", "en": "{{count}} item added to the catalog." },
        "body_few":     { "pl": "Dodano {{count}} pozycje do katalogu.", "en": "" },
        "body_other":   { "pl": "Dodano {{count}} pozycji do katalogu.", "en": "{{count}} items added to the catalog." },
        "goToCatalog":  { "pl": "Otwórz katalog",         "en": "Go to catalog" }
      },
      "error": {
        "fileTooLarge": { "pl": "Plik jest za duży – maksymalnie 5 MB.", "en": "File is too large — 5 MB maximum." },
        "tooManyRows":  { "pl": "Plik zawiera za dużo wierszy – maksymalnie 500.", "en": "File has too many rows — 500 maximum." }
      }
    },

    "itemGroupList": {
      "title":          { "pl": "Grupy pozycji",          "en": "Item groups" },
      "description":    { "pl": "Organizacyjne kontenery dla pozycji jednego typu.", "en": "Organisational containers for items of one type." },
      "createButton":   { "pl": "Nowa grupa",             "en": "New group" },
      "filter": {
        "searchPlaceholder": { "pl": "Szukaj grupy…",     "en": "Search groups…" },
        "type":              { "pl": "Typ",               "en": "Type" }
      },
      "stats": {
        "total":        { "pl": "Łącznie",                "en": "Total" },
        "empty":        { "pl": "Puste",                  "en": "Empty" },
        "byType":       { "pl": "Według typu",            "en": "By type" }
      },
      "column": {
        "name":         { "pl": "Nazwa grupy",            "en": "Group name" },
        "type":         { "pl": "Typ",                    "en": "Type" },
        "itemCount":    { "pl": "Liczba pozycji",         "en": "Items" }
      },
      "emptyAll":       { "pl": "Brak grup",              "en": "No groups yet" },
      "emptyFiltered":  { "pl": "Żadna grupa nie pasuje do filtrów", "en": "No group matches the filters" }
    },

    "itemGroupDetail": {
      "backToList":     { "pl": "Wróć do listy grup",     "en": "Back to groups" },
      "eyebrow":        { "pl": "Grupa pozycji",          "en": "Item group" },
      "editButton":     { "pl": "Edytuj grupę",           "en": "Edit group" },
      "deleteButton":   { "pl": "Usuń grupę",             "en": "Delete group" },
      "section": {
        "items_one":    { "pl": "Pozycja w grupie",       "en": "Item in this group" },
        "items_few":    { "pl": "Pozycje w grupie",       "en": "" },
        "items_other":  { "pl": "Pozycji w grupie",       "en": "Items in this group" },
        "metadata":     { "pl": "Metadane grupy",         "en": "Group metadata" },
        "audit":        { "pl": "Audyt",                  "en": "Audit" }
      },
      "metadata": {
        "name":         { "pl": "Nazwa",                  "en": "Name" },
        "type":         { "pl": "Typ",                    "en": "Type" },
        "itemCount":    { "pl": "Liczba pozycji",         "en": "Items" },
        "deletable":    { "pl": "Można usunąć",           "en": "Deletable" },
        "deletableYes": { "pl": "Tak (grupa jest pusta)", "en": "Yes (group is empty)" },
        "deletableNo":  { "pl": "Nie (grupa zawiera pozycje)", "en": "No (group has items)" }
      },
      "tableHeader": {
        "addItem":      { "pl": "Dodaj pozycję",          "en": "Add item" },
        "manage":       { "pl": "Zarządzaj pozycjami",    "en": "Manage items" }
      },
      "removeItem": {
        "confirmTitle": { "pl": "Usunąć pozycję z grupy?", "en": "Remove item from this group?" },
        "confirmBody":  { "pl": "Pozycja sama nie zostanie usunięta – pozostanie w katalogu bez przypisania do grupy.", "en": "The item itself stays in the catalog — it just becomes ungrouped." },
        "confirmCta":   { "pl": "Usuń z grupy",           "en": "Remove" }
      },
      "deleteGroup": {
        "confirmTitle": { "pl": "Usunąć grupę?",          "en": "Delete this group?" },
        "confirmBody":  { "pl": "Grupę można usunąć tylko gdy jest pusta. Powiązanych pozycji nie usuwa.", "en": "A group can be deleted only when empty. Linked items are not deleted." },
        "confirmCta":   { "pl": "Usuń grupę",             "en": "Delete group" },
        "hasMembersAlert": { "pl": "Najpierw usuń wszystkie pozycje z grupy.", "en": "Remove every item from this group first." }
      }
    },

    "itemGroupForm": {
      "createTitle":    { "pl": "Nowa grupa",             "en": "New group" },
      "editTitle":      { "pl": "Edytuj grupę",           "en": "Edit group" },
      "field": {
        "name":         { "pl": "Nazwa grupy",            "en": "Group name" },
        "type":         { "pl": "Typ pozycji",            "en": "Item type" },
        "typeLockedHint": { "pl": "Typ ustawia się raz – nie można go później zmienić.", "en": "Type is set once — it cannot be changed later." }
      }
    }
  },

  "field": {
    "code":             { "pl": "Kod",                    "en": "Code" },
    "name":             { "pl": "Nazwa",                  "en": "Name" },
    "itemType":         { "pl": "Typ",                    "en": "Type" },
    "storageUnit":      { "pl": "Jednostka magazynowa",   "en": "Storage unit" },
    "description":      { "pl": "Opis",                   "en": "Description" },
    "tags":             { "pl": "Tagi",                   "en": "Tags" },
    "tagsHint":         { "pl": "Do 20 tagów, każdy do 50 znaków.", "en": "Up to 20 tags, each up to 50 characters." },
    "shelfLifeDays":    { "pl": "Termin ważności (dni)",  "en": "Shelf life (days)" },
    "shelfLifeDaysHint":{ "pl": "Dotyczy tylko produktów i półproduktów.", "en": "Applies to products and intermediates only." },
    "ean":              { "pl": "Kod EAN",                "en": "EAN" },
    "group":            { "pl": "Grupa",                  "en": "Group" },
    "groupUngrouped":   { "pl": "Bez grupy",              "en": "Ungrouped" },
    "status":           { "pl": "Status",                 "en": "Status" }
  },

  "itemType": {
    "ingredient":       { "pl": "Surowiec",               "en": "Ingredient" },
    "product":          { "pl": "Produkt",                "en": "Product" },
    "intermediate":     { "pl": "Półprodukt",             "en": "Intermediate" },
    "packaging":        { "pl": "Opakowanie",             "en": "Packaging" },
    "consumable":       { "pl": "Materiał pomocniczy",    "en": "Consumable" },
    "service":          { "pl": "Usługa",                 "en": "Service" },
    "resale":           { "pl": "Towar handlowy",         "en": "Resale" }
  },

  "storageUnit": {
    "kg":               { "pl": "kg",                     "en": "kg" },
    "g":                { "pl": "g",                      "en": "g" },
    "l":                { "pl": "l",                      "en": "l" },
    "ml":               { "pl": "ml",                     "en": "ml" },
    "piece":            { "pl": "szt.",                   "en": "piece" }
  },

  "status": {
    "active":           { "pl": "Aktywna",                "en": "Active" },
    "inactive":         { "pl": "Zarchiwizowana",         "en": "Archived" },
    "all":              { "pl": "Wszystkie",              "en": "All" }
  },

  "deactivateError": {
    "stockRemaining":          { "pl": "Nie można dezaktywować: na stanie magazynowym pozostała ilość większa od zera.", "en": "Cannot deactivate: stock on hand is greater than zero." },
    "openProductionOrders":    { "pl": "Nie można dezaktywować: pozycja występuje w otwartych zleceniach produkcyjnych.", "en": "Cannot deactivate: the item is referenced by open production orders." },
    "inPublishedBom":          { "pl": "Nie można dezaktywować: pozycja występuje w opublikowanych recepturach (BOM).", "en": "Cannot deactivate: the item is part of published recipe BOMs." },
    "openSalesOrders":         { "pl": "Nie można dezaktywować: pozycja występuje w otwartych zleceniach sprzedaży.", "en": "Cannot deactivate: the item is referenced by open sales orders." }
  },

  "toast": {
    "created":          { "pl": "Pozycja dodana",         "en": "Item created" },
    "updated":          { "pl": "Pozycja zaktualizowana", "en": "Item updated" },
    "updatedPartial":   { "pl": "Zapisano z błędami – sprawdź wyróżnione sekcje.", "en": "Saved with errors — review highlighted sections." },
    "updateFailed":     { "pl": "Nie udało się zapisać pozycji.", "en": "Failed to save item." },
    "deactivated":      { "pl": "Pozycja zdezaktywowana", "en": "Item deactivated" },
    "reactivated":      { "pl": "Pozycja aktywowana ponownie", "en": "Item reactivated" },
    "removedFromGroup": { "pl": "Pozycję usunięto z grupy", "en": "Item removed from group" },
    "groupCreated":     { "pl": "Grupa utworzona",        "en": "Group created" },
    "groupUpdated":     { "pl": "Grupa zaktualizowana",   "en": "Group updated" },
    "groupDeleted":     { "pl": "Grupa usunięta",         "en": "Group deleted" }
  }
}

Notes for implementers

  • The JSON above is the proposed shape for src/locales/{en,pl}/item-catalog.json. Each leaf currently holds an { pl, en } pair only for review readability — in the actual locale files, English lives in src/locales/en/item-catalog.json and Polish in src/locales/pl/item-catalog.json, each as a flat per-language JSON tree mirroring the structure above. Use Polish drafts as the starting point but treat them as drafts: validate against the rendered UI during the M1+ tickets and refine.
  • Polish empty strings on the _few plural rules above mean the English entry is identical to _other (English has no _few); the JSON files will simply omit _few for en.
  • The seed translation set for M0 ticket 12 is the union of the leaves under page.itemList, page.itemGroupList, field.*, itemType.*, storageUnit.*, status.*, toast.*, deactivateError.*. Other keys are added by their owning ticket.
  • Sidebar labels (common:nav.catalog, common:nav.items, common:nav.groups) live in src/locales/{en,pl}/common.json — added during M0 ticket 12 alongside the NavItems.
  • t('item-catalog:itemType.${item.item_type}') and t('item-catalog:storageUnit.${item.storage_unit}') are the canonical patterns for translating BE enum values — never hand-write switch (item.item_type) strings.